Número máximo de valores en enum Parte I

Primera parte, teórica | Segunda parte, práctica


Basado en un tweet de Evgeny Mandrikov, también conocido como godin :


En él, se pregunta qué número máximo de valores se puede especificar en una enum en Java. Después de una serie de experimentos y el uso de la magia negra ConstantDynamic ( JEP 309 ), el autor de la pregunta llega al número 8191.

En una serie de dos artículos, buscamos los límites teóricos sobre el número de elementos en una enumeración, tratamos de acercarnos a ellos en la práctica y descubrimos en el camino cómo JEP 309 puede ayudar.

Reconocimiento


Un capítulo de revisión en el que primero vemos la enumeración desmontada.

Primero, veamos en qué se traduce la siguiente enumeración:

 public enum FizzBuzz { Fizz, Buzz, FizzBuzz; } 

Después de compilar y desarmar:

javap -c -s -p -v FizzBuzz.class
 Classfile /dev/null/FizzBuzz.class Last modified 32 . 2019 .; size 903 bytes MD5 checksum add0af79de3e9a70a7bbf7d57dd0cfe7 Compiled from "FizzBuzz.java" public final class FizzBuzz extends java.lang.Enum<FizzBuzz> minor version: 0 major version: 58 flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM this_class: #2 // FizzBuzz super_class: #13 // java/lang/Enum interfaces: 0, fields: 4, methods: 4, attributes: 2 Constant pool: #1 = Fieldref #2.#3 // FizzBuzz.$VALUES:[LFizzBuzz; #2 = Class #4 // FizzBuzz #3 = NameAndType #5:#6 // $VALUES:[LFizzBuzz; #4 = Utf8 FizzBuzz #5 = Utf8 $VALUES #6 = Utf8 [LFizzBuzz; #7 = Methodref #8.#9 // "[LFizzBuzz;".clone:()Ljava/lang/Object; #8 = Class #6 // "[LFizzBuzz;" #9 = NameAndType #10:#11 // clone:()Ljava/lang/Object; #10 = Utf8 clone #11 = Utf8 ()Ljava/lang/Object; #12 = Methodref #13.#14 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #13 = Class #15 // java/lang/Enum #14 = NameAndType #16:#17 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #15 = Utf8 java/lang/Enum #16 = Utf8 valueOf #17 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #18 = Methodref #13.#19 // java/lang/Enum."<init>":(Ljava/lang/String;I)V #19 = NameAndType #20:#21 // "<init>":(Ljava/lang/String;I)V #20 = Utf8 <init> #21 = Utf8 (Ljava/lang/String;I)V #22 = String #23 // Fizz #23 = Utf8 Fizz #24 = Methodref #2.#19 // FizzBuzz."<init>":(Ljava/lang/String;I)V #25 = Fieldref #2.#26 // FizzBuzz.Fizz:LFizzBuzz; #26 = NameAndType #23:#27 // Fizz:LFizzBuzz; #27 = Utf8 LFizzBuzz; #28 = String #29 // Buzz #29 = Utf8 Buzz #30 = Fieldref #2.#31 // FizzBuzz.Buzz:LFizzBuzz; #31 = NameAndType #29:#27 // Buzz:LFizzBuzz; #32 = String #4 // FizzBuzz #33 = Fieldref #2.#34 // FizzBuzz.FizzBuzz:LFizzBuzz; #34 = NameAndType #4:#27 // FizzBuzz:LFizzBuzz; #35 = Utf8 values #36 = Utf8 ()[LFizzBuzz; #37 = Utf8 Code #38 = Utf8 LineNumberTable #39 = Utf8 (Ljava/lang/String;)LFizzBuzz; #40 = Utf8 LocalVariableTable #41 = Utf8 name #42 = Utf8 Ljava/lang/String; #43 = Utf8 this #44 = Utf8 Signature #45 = Utf8 ()V #46 = Utf8 <clinit> #47 = Utf8 Ljava/lang/Enum<LFizzBuzz;>; #48 = Utf8 SourceFile #49 = Utf8 FizzBuzz.java { public static final FizzBuzz Fizz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final FizzBuzz Buzz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final FizzBuzz FizzBuzz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM private static final FizzBuzz[] $VALUES; descriptor: [LFizzBuzz; flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC public static FizzBuzz[] values(); descriptor: ()[LFizzBuzz; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LFizzBuzz; 3: invokevirtual #7 // Method "[LFizzBuzz;".clone:()Ljava/lang/Object; 6: checkcast #8 // class "[LFizzBuzz;" 9: areturn LineNumberTable: line 1: 0 public static FizzBuzz valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LFizzBuzz; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #2 // class FizzBuzz 2: aload_0 3: invokestatic #12 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #2 // class FizzBuzz 9: areturn LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 name Ljava/lang/String; private FizzBuzz(); descriptor: (Ljava/lang/String;I)V flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=3, args_size=3 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #18 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 6: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this LFizzBuzz; Signature: #45 // ()V static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #2 // class FizzBuzz 3: dup 4: ldc #22 // String Fizz 6: iconst_0 7: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #25 // Field Fizz:LFizzBuzz; 13: new #2 // class FizzBuzz 16: dup 17: ldc #28 // String Buzz 19: iconst_1 20: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 23: putstatic #30 // Field Buzz:LFizzBuzz; 26: new #2 // class FizzBuzz 29: dup 30: ldc #32 // String FizzBuzz 32: iconst_2 33: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 36: putstatic #33 // Field FizzBuzz:LFizzBuzz; 39: iconst_3 40: anewarray #2 // class FizzBuzz 43: dup 44: iconst_0 45: getstatic #25 // Field Fizz:LFizzBuzz; 48: aastore 49: dup 50: iconst_1 51: getstatic #30 // Field Buzz:LFizzBuzz; 54: aastore 55: dup 56: iconst_2 57: getstatic #33 // Field FizzBuzz:LFizzBuzz; 60: aastore 61: putstatic #1 // Field $VALUES:[LFizzBuzz; 64: return LineNumberTable: line 3: 0 line 5: 13 line 7: 26 line 1: 39 } Signature: #47 // Ljava/lang/Enum<LFizzBuzz;>; SourceFile: "FizzBuzz.java" 


En el listado nos encontramos

  • Un campo public static final para cada valor definido en la enumeración
  • Campo sintético privado $VALUES , detalle de implementación del método de values()
  • Implementación de los métodos values() y valueOf()
  • Constructor privado
  • El bloque de inicialización estática, donde en realidad sucede lo más interesante. Consideremos con más detalle.

En forma de código Java, este último se parece a esto:

  static { Fizz = new FizzBuzz("Fizz", 0); Buzz = new FizzBuzz("Buzz", 1); FizzBuzz = new FizzBuzz("FizzBuzz", 2); $VALUES = new FizzBuzz[] { Fizz, Buzz, FizzBuzz }; } 

Primero, se crean instancias de elementos de enumeración. Las instancias creadas se escriben inmediatamente en los campos public static final correspondientes.

Luego se crea una matriz y se completa con enlaces a instancias de todos los elementos de enumeración. Los enlaces se toman de los campos de la clase que inicializamos en el párrafo anterior. La matriz llena se almacena en el campo private static final $VALUES .

Después de eso, la lista está lista para comenzar.

Cuello de botella


Un capítulo aburrido en el que buscamos restricciones en el número de elementos de enumeración.

Puede comenzar su búsqueda con el capítulo JLS §8.9.3 "Miembros de Enum":

JLS 8.9.3 Miembros de Enum
Los miembros de una enumeración tipo E son todos los siguientes:
...
* Para cada enum constante c declarada en el cuerpo de la declaración de E, E tiene
un campo final estático público declarado implícitamente de tipo E que tiene el mismo
nombre como c. El campo tiene un inicializador variable que crea una instancia de E y pasa cualquier
argumentos de c para el constructor elegido para E. El campo tiene las mismas anotaciones
como c (si existe).

Estos campos se declaran implícitamente en el mismo orden que el correspondiente
constantes enum, antes de cualquier campo estático declarado explícitamente en el cuerpo del
declaración de E.
...
* Los siguientes métodos declarados implícitamente:
 /** * Returns an array containing the constants of this enum * type, in the order they're declared. This method may be * used to iterate over the constants as follows: * * for(E c : E.values()) * System.out.println(c); * * @return an array containing the constants of this enum * type, in the order they're declared */ public static E[] values(); /** * Returns the enum constant of this type with the specified * name. * The string must match exactly an identifier used to declare * an enum constant in this type. (Extraneous whitespace * characters are not permitted.) * * @return the enum constant with the specified name * @throws IllegalArgumentException if this enum type has no * constant with the specified name */ public static E valueOf(String name); 



Por lo tanto, cada clase de enumeración tiene un método de values() que devuelve una matriz con todos los elementos declarados en esta enumeración. De ello se deduce que una enumeración esférica en el vacío no puede contener más de elementos Integer.MAX_VALUE + 1 .

Siguiendo adelante. Las enumeraciones en Java se representan como descendientes de la clase java.lang.Enum y, por lo tanto, están sujetas a todas las restricciones inherentes a las clases en la JVM.

Veamos la descripción de alto nivel de la estructura del archivo de clase que figura en JVMS §4.1 "La estructura de archivos de clase":

 ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 

Como ya sabemos por JLS §8.9.3, se crea un campo con el mismo nombre para cada elemento de enumeración en la clase resultante. El número de campos en la clase define un número de campos sin signo de 16 bits, lo que nos limita a 65_535 campos en un archivo de clase o 65_534 elementos de enumeración. Un campo está reservado para la matriz $VALUES , cuyo clon devuelve el método values() . Esto no se establece explícitamente en la especificación, pero es poco probable que se encuentre con una solución más elegante.

Los nombres de los campos, métodos, clases, valores constantes y mucho más se almacenan en el grupo constante.
Si no sabe nada sobre la estructura interna del grupo constante, le recomiendo leer el artículo antiguo de lany . A pesar de que desde su escritura en el grupo de constantes han aparecido muchas cosas nuevas e interesantes, los principios básicos permanecen sin cambios.
El tamaño del grupo de constantes de clase también está limitado por el número de elementos 65_535. El conjunto de constantes de una clase formada correctamente nunca está vacío. Como mínimo, habrá un nombre para esta clase.

Por ejemplo, el grupo de constantes de la clase de enumeración vacía compilada por javac desde OpenJDK 14-ea + 29 sin información de depuración contiene 29 entradas.

De ello se deduce que el número de 65_534 elementos en una enumeración también es inalcanzable. En el mejor de los casos, podemos contar con 65_505 o un número cercano a este.

El último acorde en esta introducción prolongada:

El valor se puede <clinit> en el campo static final solo en el bloque de inicialización estático, que se representa en el nivel del archivo de clase mediante un método llamado <clinit> . El código de bytes de cualquier método no puede ocupar más de 65_535 bytes. Un número familiar, ¿no?

Una instrucción de escritura estática putstatic toma 3 bytes, lo que nos da una estimación aproximada de 65_535 / 3 = 21_845 . De hecho, esta estimación está exagerada. La instrucción toma el valor para escribir en el campo desde la parte superior de la pila, que una de las instrucciones anteriores colocó allí. Y esta instrucción también toma bytes preciosos. Pero incluso si no tiene esto en cuenta, el número resultante sigue siendo significativamente menor que 65_505.

En resumen:

  • El formato de archivo de clase limita el número máximo de elementos de enumeración a aproximadamente 65_505
  • El mecanismo de inicialización de campo final estático nos limita aún más. Teóricamente: hasta 21_845 elementos como máximo, en la práctica, este número es aún menor

En el artículo final de la serie, nos centraremos en la optimización poco saludable y la generación de archivos de clase.

Source: https://habr.com/ru/post/483392/


All Articles