Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the Enum#values() allocate memory on each call?

I need to convert an ordinal int value to an enum value in Java. Which is simple:

MyEnumType value = MyEnumType.values()[ordinal]; 

The values() method is implicit, and I cannot locate the source code for it, hence the question.

Does the MyEnumType.values() allocate a new array or not? And if it does, should I cache the array when first called? Suppose that the conversion will be called quite often.

like image 810
plastique Avatar asked Sep 26 '15 16:09

plastique


People also ask

What is enum used for?

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week.

What does enum do in C?

Enumeration or Enum in C is a special kind of data type defined by the user. It consists of constant integrals or integers that are given names by a user. The use of enum in C to name the integer values makes the entire program easy to learn, understand, and maintain by the same or even different programmer.

What is enum Mcq?

Explanation: Enumeration (enum) is a user defined data type in C. It is used to assign names to integral constants. The names make a program easy to read and maintain.

Why enum is used in Java?

Enums are used when we know all possible values at compile time, such as choices on a menu, rounding modes, command-line flags, etc. It is not necessary that the set of constants in an enum type stay fixed for all time. A Java enumeration is a class type.


2 Answers

Yes.

Java doesn't have mechanism which lets us create unmodifiable array. So if values() would return same mutable array, we risk that someone could change its content for everyone.

So until unmodifiable arrays will be introduced to Java, for safety values() must return new/separate array holding all values.

We can test it with == operator:

MyEnumType[] arr1 = MyEnumType.values(); MyEnumType[] arr2 = MyEnumType.values(); System.out.println(arr1 == arr2);       //false 

If you want to avoid recreating this array you can simply store it and reuse result of values() later. There are few ways to do it, like.

  • you can create private array and allow access to its content only via getter method like

    private static final MyEnumType[] VALUES = values();// to avoid recreating array  MyEnumType getByOrdinal(int){     return VALUES[int]; } 
  • you can store result of values() in unmodifiable collection like List to ensure that its content will not be changed (now such list can be public).

    public static final List<MyEnumType> VALUES = Collections.unmodifiableList(Arrays.asList(values())); 
like image 87
Pshemo Avatar answered Sep 22 '22 07:09

Pshemo


Theoretically, the values() method must return a new array every time, since Java doesn't have immutable arrays. If it always returned the same array it could not prevent callers muddling each other up by modifying the array.

I cannot locate the source code for it

The values() method has no ordinary source code, being compiler-generated. For javac, the code that generates the values() method is in com.sun.tools.javac.comp.Lower.visitEnumDef. For ECJ (Eclipse's compiler), the code is in org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateSyntheticBodyForEnumValues.

An easier way to find the implementation of the values() method is by disassembling a compiled enum. First create some silly enum:

enum MyEnumType {     A, B, C;      public static void main(String[] args) {         System.out.println(values()[0]);     } } 

Then compile it, and disassemble it using the javap tool included in the JDK:

javac MyEnumType.java && javap -c -p MyEnumType 

Visible in the output are all the compiler-generated implicit members of the enum, including (1) a static final field for each enum constant, (2) a hidden $VALUES array containing all the constants, (3) a static initializer block that instantiates each constant and assigns each one to its named field and to the array, and (4) the values() method that works by calling .clone() on the $VALUES array and returning the result:

final class MyEnumType extends java.lang.Enum<MyEnumType> {   public static final MyEnumType A;    public static final MyEnumType B;    public static final MyEnumType C;    private static final MyEnumType[] $VALUES;    public static MyEnumType[] values();     Code:        0: getstatic     #1                  // Field $VALUES:[LMyEnumType;        3: invokevirtual #2                  // Method "[LMyEnumType;".clone:()Ljava/lang/Object;        6: checkcast     #3                  // class "[LMyEnumType;"        9: areturn    public static MyEnumType valueOf(java.lang.String);     Code:        0: ldc           #4                  // class MyEnumType        2: aload_0        3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;        6: checkcast     #4                  // class MyEnumType        9: areturn    private MyEnumType(java.lang.String, int);     Code:        0: aload_0        1: aload_1        2: iload_2        3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V        6: return    public static void main(java.lang.String[]);     Code:        0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;        3: invokestatic  #8                  // Method values:()[LMyEnumType;        6: iconst_0        7: aaload        8: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V       11: return    static {};     Code:        0: new           #4                  // class MyEnumType        3: dup        4: ldc           #10                 // String A        6: iconst_0        7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V       10: putstatic     #12                 // Field A:LMyEnumType;       13: new           #4                  // class MyEnumType       16: dup       17: ldc           #13                 // String B       19: iconst_1       20: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V       23: putstatic     #14                 // Field B:LMyEnumType;       26: new           #4                  // class MyEnumType       29: dup       30: ldc           #15                 // String C       32: iconst_2       33: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V       36: putstatic     #16                 // Field C:LMyEnumType;       39: iconst_3       40: anewarray     #4                  // class MyEnumType       43: dup       44: iconst_0       45: getstatic     #12                 // Field A:LMyEnumType;       48: aastore       49: dup       50: iconst_1       51: getstatic     #14                 // Field B:LMyEnumType;       54: aastore       55: dup       56: iconst_2       57: getstatic     #16                 // Field C:LMyEnumType;       60: aastore       61: putstatic     #1                  // Field $VALUES:[LMyEnumType;       64: return } 

However, the fact that the values() method has to return a new array, doesn't mean the compiler has to use the method. Potentially a compiler could detect use of MyEnumType.values()[ordinal] and, seeing that the array is not modified, it could bypass the method and use the underlying $VALUES array. The above disassembly of the main method shows that javac does not make such an optimization.

I also tested ECJ. The disassembly shows ECJ also initializes a hidden array to store the constants (although the Java langspec doesn't require that), but interestingly its values() method prefers to create a blank array then fill it with System.arraycopy, rather than calling .clone(). Either way, values() returns a new array every time. Like javac, it doesn't attempt to optimize the ordinal lookup:

final class MyEnumType extends java.lang.Enum<MyEnumType> {   public static final MyEnumType A;    public static final MyEnumType B;    public static final MyEnumType C;    private static final MyEnumType[] ENUM$VALUES;    static {};     Code:        0: new           #1                  // class MyEnumType        3: dup        4: ldc           #14                 // String A        6: iconst_0        7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V       10: putstatic     #19                 // Field A:LMyEnumType;       13: new           #1                  // class MyEnumType       16: dup       17: ldc           #21                 // String B       19: iconst_1       20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V       23: putstatic     #22                 // Field B:LMyEnumType;       26: new           #1                  // class MyEnumType       29: dup       30: ldc           #24                 // String C       32: iconst_2       33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V       36: putstatic     #25                 // Field C:LMyEnumType;       39: iconst_3       40: anewarray     #1                  // class MyEnumType       43: dup       44: iconst_0       45: getstatic     #19                 // Field A:LMyEnumType;       48: aastore       49: dup       50: iconst_1       51: getstatic     #22                 // Field B:LMyEnumType;       54: aastore       55: dup       56: iconst_2       57: getstatic     #25                 // Field C:LMyEnumType;       60: aastore       61: putstatic     #27                 // Field ENUM$VALUES:[LMyEnumType;       64: return    private MyEnumType(java.lang.String, int);     Code:        0: aload_0        1: aload_1        2: iload_2        3: invokespecial #31                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V        6: return    public static void main(java.lang.String[]);     Code:        0: getstatic     #35                 // Field java/lang/System.out:Ljava/io/PrintStream;        3: invokestatic  #41                 // Method values:()[LMyEnumType;        6: iconst_0        7: aaload        8: invokevirtual #45                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V       11: return    public static MyEnumType[] values();     Code:        0: getstatic     #27                 // Field ENUM$VALUES:[LMyEnumType;        3: dup        4: astore_0        5: iconst_0        6: aload_0        7: arraylength        8: dup        9: istore_1       10: anewarray     #1                  // class MyEnumType       13: dup       14: astore_2       15: iconst_0       16: iload_1       17: invokestatic  #53                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V       20: aload_2       21: areturn    public static MyEnumType valueOf(java.lang.String);     Code:        0: ldc           #1                  // class MyEnumType        2: aload_0        3: invokestatic  #59                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;        6: checkcast     #1                  // class MyEnumType        9: areturn } 

However, it's still potentially possible that the JVM could have an optimization that detects the fact that the array is copied and then thrown away, and avoids it. To test that, I ran the following pair of benchmark programs that test ordinal lookup in a loop, one which calls values() each time and the other that uses a private copy of the array. The result of the ordinal lookup is assigned to a volatile field to prevent it being optimized away:

enum MyEnumType1 {     A, B, C;      public static void main(String[] args) {         long t = System.nanoTime();         for (int n = 0; n < 100_000_000; n++) {             for (int i = 0; i < 3; i++) {                 dummy = values()[i];             }         }         System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);     }      public static volatile Object dummy; }  enum MyEnumType2 {     A, B, C;      public static void main(String[] args) {         long t = System.nanoTime();         for (int n = 0; n < 100_000_000; n++) {             for (int i = 0; i < 3; i++) {                 dummy = values[i];             }         }         System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);     }      public static volatile Object dummy;     private static final MyEnumType2[] values = values(); } 

I ran this on Java 8u60, on the Server VM. Each test using the values() method took around 10 seconds, while each test using the private array took around 2 seconds. Using the -verbose:gc JVM argument showed there was significant garbage collection activity when the values() method was used, and none when using the private array. Running the same tests on the Client VM, the private array was still fast, but the values() method became even slower, taking over a minute to finish. Calling values() also took longer the more enum constants were defined. All this indicates that the values() method really does allocate a new array each time, and that avoiding it can be advantageous.

Note that both java.util.EnumSet and java.util.EnumMap need to use the array of enum constants. For performance they call JRE proprietary code that caches the result of values() in a shared array stored in java.lang.Class. You can get access to that shared array yourself by calling sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class), but it is unsafe to depend on it as such APIs are not part of any spec and can be changed or removed in any Java update.

Conclusion:

  • The enum values() method has to behave as if it always allocates a new array, in case callers modify it.
  • Compilers or VMs could potentially optimize that allocation away in some cases, but apparently they don't.
  • In performance-critical code, it is well worth taking your own copy of the array.
like image 20
Boann Avatar answered Sep 23 '22 07:09

Boann