为什么说枚举更占内存,枚举原理是什么?

简介: 为什么说枚举更占内存,枚举原理是什么? 从以前学习java 开始就听说枚举很占内存,然后老版Android开发指南文章也指出,枚举通常需要比静态常量多两倍的内存。你应该严格避免在android上使用枚举。

为什么说枚举更占内存,枚举原理是什么?

从以前学习java 开始就听说枚举很占内存,然后老版Android开发指南文章也指出,枚举通常需要比静态常量多两倍的内存。你应该严格避免在android上使用枚举。那么究竟为什么说枚举更占内存呢?本文就是通过这种方法来分析枚举为什么占内存的,而不是说拒绝枚举。

关于 Enum

Enum 一般用来表示一组相同类型可列举的常量。如性别、日期、月份、颜色等。对这些属性用常量的好处是显而易见的,不仅可以保证单例,且比较时候可以用 ”==” 来替换 equals

基本使用

看看枚举的基本使用

public enum Color {   

  RED , BLUE,GREEN,BLACK ;   

}  

原理分析

通常会使用javap -c Color.class 反编译class 文件,查看生成的字节码,如下:

Compiled from "Color.java"
public final class org.fast.clean.Color extends java.lang.Enum<org.fast.clean.Color> {
  public static final org.fast.clean.Color RED;

  public static final org.fast.clean.Color BLUE;

  public static final org.fast.clean.Color GREEN;

  public static final org.fast.clean.Color BLACK;

  public static org.fast.clean.Color[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lorg/fast/clean/Color;
       3: invokevirtual #2                  // Method "[Lorg/fast/clean/Color;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lorg/fast/clean/Color;"
       9: areturn

  public static org.fast.clean.Color valueOf(java.lang.String);
    Code:
     ....
            9: areturn

  static {};
    Code:
       0: new           #4                  // class org/fast/clean/Color
       3: dup
       4: ldc           #7                  // String RED
       6: iconst_0
       7: invokespecial #8                  // Method "<init>":(Ljava/lang/
       ....
      10: putstatic     #9                  // Field RED:Lorg/fast/clean/Color;
      13: new           #4                  // class org/fast/clean/Color
      16: dup
      17: ldc           #10                 // String BLUE
          83: return
}

org.fast.clean.Color 类被编译成继承自继承自java.lang.Enumfinal类,其中的成员变量也是final常量,它们都不能被修改的。

下面使用jad 反编译生成jad源码文件, 文件显示每个方法的字节码的实际作用,与源码做出对比。

public final class Color extends Enum
{

    public static Color[] values()
    {
        return (Color[])$VALUES.clone();
    }
    public static Color valueOf(String s)
    {
        return (Color)Enum.valueOf(org/fast/clean/Color, s);
    }
    private Color(String s, int i)
    {
        super(s, i);
    }
    public static final Color RED;
    public static final Color BLUE;
    public static final Color GREEN;
    public static final Color BLACK;
    private static final Color $VALUES[];
    static 
    {
        RED = new Color("RED", 0);
        BLUE = new Color("BLUE", 1);
        GREEN = new Color("GREEN", 2);
        BLACK = new Color("BLACK", 3);
        $VALUES = (new Color[] {
            RED, BLUE, GREEN, BLACK
        });
    }
}

通过比较字节码和源代码的区别,我们可以知道为什么枚举确实比简单的静态变量占用的内存要更多。

Android 简单替代枚举的方法

严格来讲,这个使用方法是有很多缺陷的,但是使用下面的方法就能满足需求的话,那么用Java Enum是会带来各种更大的开销。

  • 方法一:使用接口变量

接口变量默认都是public static final的,个人理解接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现implements)开放,接口是对开闭原则的一种体现。

public interface ErrorCode {

    int ERROR_MANUAL_EXP = 100;

    int ERROR_MANUAL_BACK = 101;
}

使用javap Color.class 反编译class 文件,查看生成的字节码,如下:

Compiled from "ErrorCode.java"
interface org.fast.clean.ErrorCode {
  public static final int ERROR_MANUAL_EXP;
  public static final int ERROR_MANUAL_BACK;
}

可以看出就是一个public static final的静态变量。

  • 方法二:使用support-annotations 注解库

Android Support Library19.1版本开始引入了一个新的注解库,使用 com.android.support:support-annotations ,这个官方的注解支持库中包含了许多很好的注解,可以帮助我们在编译的时候就找到错误。IntDefStringDef 是包含在库中的两个关于常量的注解,我们可以用来代替枚举其中包括了很多有用的元注解,可以用来修饰代码,如@NonNull@StringRes@IntDef@StringDef等等

下面我们使用@IntDef来替代枚举,方法如下

  public static final int RED = 0;
    public static final int BLUE = 1;
    public static final int GREEN = 2;

    @IntDef({RED, BLUE, GREEN})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Colors {
    }

gradle 依赖:

dependencies { compile ‘com.android.support:support-annotations:24.2.0’ }

等等还有其他方法。

枚举单例

枚举在单例的使用也是很平常的


    public enum SingleTon {
        INSTANCE;
    }

    //单例对象的获取:
    SingleTon instance = SingleTon.INSTANCE;

通过查看java.lang.Enum源码如下:

 /**
     * prevent default deserialization
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }

对于单例是否安全,主要考虑以下两方面:序列化和反序列方面、线程安全方面。

  • 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。
  • 对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。

所以使用枚举也是一种比较好的单例模式,通过反编译我们知道,缺点就是不能够继承,因为final了。

通过上面的分析枚举,我们只是讨论为什么说枚举更占内存,枚举原理是什么,而不是让我们拒绝使用枚举,因为就几个枚举,让应用内存开销大,那就实在是太可怕了。。。更重要的是通过比较字节码和源代码,我们可以发现很多的问题,一个很重要的作用就是了解很多编译器内部的工作机制。

原文地址http://www.bieryun.com/2974.html

相关文章
|
1天前
|
存储 Linux 程序员
【操作系统原理】—— Linux内存管理
【操作系统原理】—— Linux内存管理
4 0
|
7天前
|
Arthas 监控 Java
JVM工作原理与实战(三十一):诊断内存泄漏的原因
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了诊断内存溢出的原因、MAT内存泄漏检测的原理等内容。
13 0
|
7天前
|
存储 Arthas 监控
JVM工作原理与实战(三十):堆内存状况的对比分析
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了堆内存状况的对比分析、产生内存溢出的原因等内容。
13 0
|
7天前
|
Arthas Prometheus 监控
JVM工作原理与实战(二十九):监控内存泄漏的工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了解决内存溢出的步骤、Top命令、VisualVM、Arthas、Prometheus + Grafana等内容。
16 0
|
7天前
|
监控 Java 测试技术
JVM工作原理与实战(二十八):内存溢出和内存泄漏
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了内存溢出与内存泄漏、内存泄漏的常见场景、解决内存溢出的步骤等内容。
10 0
JVM工作原理与实战(二十八):内存溢出和内存泄漏
|
7天前
|
监控 安全 Java
JVM工作原理与实战(二十一):内存管理
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了不同语言的内存管理(C/C++、Java)、垃圾回收的对比(自动垃圾回收与手动垃圾回收)等内容。
12 0
|
7天前
|
Arthas 存储 监控
JVM工作原理与实战(二十):直接内存
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了直接内存、在直接内存上创建数据等内容。
10 0
|
7天前
|
存储 监控 Java
JVM工作原理与实战(十七):运行时数据区-栈内存溢出
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了栈内存溢出、设置虚拟机栈的大小等内容。
12 0
|
10天前
|
算法 安全 Linux
深入理解操作系统的内存管理:原理与实践
【5月更文挑战第4天】 在现代计算领域,操作系统的内存管理是保证系统稳定、高效运行的关键。本文旨在深入剖析操作系统内存管理的基本原理,包括虚拟内存、物理内存、分页机制和内存交换等概念。同时,文章将探讨如何通过优化内存分配策略,提升系统性能及响应速度。此外,还将讨论当前流行的操作系统(如Linux和Windows)中内存管理的创新技术及其应用案例。
|
14天前
|
缓存 算法 内存技术
深入理解操作系统内存管理:原理与实践
【4月更文挑战第30天】本文旨在深入探讨操作系统中的内存管理机制,包括物理内存的分配与回收、虚拟内存技术、分页系统以及内存优化策略。通过对内存管理概念的详细解读和实际案例分析,读者将获得对操作系统如何处理内存资源的全面认识,并了解如何在实践中应用这些知识以提高系统性能和稳定性。