Maximale Anzahl von Werten in Teil I der AufzÀhlung

Erster Teil, Theoretisch | Zweiter Teil, Praktisch


Basierend auf einem Tweet von Evgeny Mandrikov aka godin :


Darin fragt er sich, welche maximale Anzahl von Werten in einer enum in Java angegeben werden kann. Nach einer Reihe von Experimenten und der Verwendung von schwarzer Magie ConstantDynamic ( JEP 309 ) kommt der Autor der Frage auf die Nummer 8191.

In einer Reihe von zwei Artikeln untersuchen wir die theoretischen Grenzen der Anzahl der Elemente in einer AufzÀhlung, versuchen, sie in der Praxis nÀher zu bringen und herauszufinden, wie JEP 309 helfen kann.

AufklÀrung


Ein Wiederholungskapitel, in dem wir die AufzÀhlung zuerst zerlegt sehen.

Lassen Sie uns zunÀchst sehen, was die folgende AufzÀhlung bedeutet:

 public enum FizzBuzz { Fizz, Buzz, FizzBuzz; } 

Nach dem Kompilieren und Disassemblieren:

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" 


In der Auflistung werden wir getroffen

  • Ein public static final Endfeld fĂŒr jeden in der AufzĂ€hlung definierten Wert
  • Privates synthetisches Feld $VALUES , Implementierungsdetail der Methode values()
  • Implementierung der Methoden values() und valueOf()
  • Privatbauer
  • Der Block der statischen Initialisierung, in dem eigentlich das Interessanteste passiert. Lassen Sie es uns genauer betrachten.

In Form von Java-Code sieht letzterer ungefĂ€hr so ​​aus:

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

ZunÀchst werden Instanzen von AufzÀhlungselementen erstellt. Erstellte Instanzen werden sofort in die entsprechenden public static final Endfelder geschrieben.

Anschließend wird ein Array erstellt und mit Links zu Instanzen aller AufzĂ€hlungselemente gefĂŒllt. Links werden aus den Feldern der Klasse entnommen, die wir im obigen Absatz initialisiert haben. Das gefĂŒllte Array wird im private static final $VALUES .

Danach ist die Auflistung bereit zu gehen.

Engpass


Ein langweiliges Kapitel, in dem wir nach EinschrĂ€nkungen fĂŒr die Anzahl der AufzĂ€hlungselemente suchen.

Sie können Ihre Suche mit dem JLS- Kapitel §8.9.3 „Mitglieder aufzĂ€hlen “ beginnen:

JLS 8.9.3 Mitglieder aufzÀhlen
Die Mitglieder eines AufzÀhlungstyps E sind alle der folgenden:
...
* FĂŒr jede im Hauptteil der Deklaration von E deklarierte Enum-Konstante c hat E
ein implizit deklariertes öffentliches statisches Endfeld vom Typ E, das dasselbe hat
Name wie c. Das Feld hat einen Variableninitialisierer, der E instanziiert und any ĂŒbergibt
Argumente von c an den fĂŒr E gewĂ€hlten Konstruktor. Das Feld hat dieselben Annotationen
als c (falls vorhanden).

Diese Felder werden implizit in derselben Reihenfolge deklariert wie die entsprechenden
enum Konstanten vor statischen Feldern, die explizit im Body des deklariert sind
ErklÀrung von E.
...
* Die folgenden implizit deklarierten Methoden:
 /** * 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); 



Daher verfĂŒgt jede AufzĂ€hlungsklasse ĂŒber eine values() -Methode, die ein Array mit allen in dieser AufzĂ€hlung deklarierten Elementen zurĂŒckgibt. Daraus folgt, dass eine sphĂ€rische AufzĂ€hlung in einem Vakuum nicht mehr als Integer.MAX_VALUE + 1 Elemente enthalten kann.

Weitermachen. AufzÀhlungen in Java werden als Nachkommen der Klasse java.lang.Enum und unterliegen daher allen EinschrÀnkungen, die Klassen in der JVM unterliegen.

Schauen wir uns die allgemeine Beschreibung der Struktur der Klassendatei an, die in JVMS §4.1 „Die ClassFile-Struktur“ angegeben ist:

 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]; } 

Wie wir bereits aus JLS §8.9.3 wissen, wird fĂŒr jedes AufzĂ€hlungselement in der resultierenden Klasse ein gleichnamiges Feld erstellt. Die Anzahl der Felder in der Klasse definiert einen 16-Bit- fields_count fĂŒr fields_count ohne fields_count , der uns auf 65_535 Felder in einer Klassendatei oder 65_534 AufzĂ€hlungselementen beschrĂ€nkt. Ein Feld ist fĂŒr das Array $VALUES reserviert, von dem ein Klon die Methode values() zurĂŒckgibt. Dies wird in der Spezifikation nicht explizit angegeben, es ist jedoch unwahrscheinlich, dass eine elegantere Lösung gefunden wird.

Die Namen von Feldern, Methoden, Klassen, Konstanten und vielem mehr werden im Konstantenpool gespeichert.
Wenn Sie nichts ĂŒber die interne Struktur des konstanten Pools wissen, empfehle ich, den alten Artikel von Lany zu lesen . Trotz der Tatsache, dass seit dem Schreiben im Konstantenpool viele neue und interessante Dinge aufgetaucht sind, bleiben die Grundprinzipien unverĂ€ndert.
Die GrĂ¶ĂŸe des Pools von Klassenkonstanten ist auch durch die Anzahl von 65_535 Elementen begrenzt. Der Konstantenpool einer korrekt gebildeten Klasse ist niemals leer. Es wird mindestens einen Namen fĂŒr diese Klasse geben.

Beispielsweise enthÀlt der Konstantenpool der Klasse der leeren Enumeration, die von javac aus OpenJDK 14-ea + 29 ohne Debugging-Informationen kompiliert wurde, 29 EintrÀge.

Daraus folgt, dass die Anzahl von 65_534 Elementen in einer AufzÀhlung ebenfalls nicht erreichbar ist. Im besten Fall können wir auf 65_505 oder eine Zahl in der NÀhe davon zÀhlen.

Der letzte Akkord in dieser langwierigen EinfĂŒhrung:

Der Wert kann nur im statischen Initialisierungsblock in das statische Endfeld geschrieben werden, der auf der Ebene der Klassendatei durch eine Methode namens <clinit> . Der Bytecode einer Methode kann nicht mehr als 65_535 Byte belegen. Eine vertraute Nummer, nicht wahr?

Eine statische putstatic Schreibanweisung benötigt 3 Bytes, was eine grobe SchĂ€tzung von 65_535 / 3 = 21_845 . TatsĂ€chlich ist diese SchĂ€tzung ĂŒberbewertet. Der Befehl nimmt den Wert an, der vom oberen Rand des Stapels in das Feld geschrieben werden soll, den einer der vorherigen Befehle dort platziert hat. Und diese Anweisung belegt auch wertvolle Bytes. Aber selbst wenn dies nicht berĂŒcksichtigt wird, ist die resultierende Zahl immer noch deutlich kleiner als 65_505.

Zusammenfassend:

  • Das Klassendateiformat begrenzt die maximale Anzahl von AufzĂ€hlungselementen auf ungefĂ€hr 65_505
  • Der statische Initialisierungsmechanismus fĂŒr das endgĂŒltige Feld schrĂ€nkt uns noch mehr ein. Theoretisch - maximal 21_845 Elemente, in der Praxis ist diese Anzahl sogar noch geringer

Im letzten Artikel der Reihe werden wir uns auf die fehlerhafte Optimierung und Generierung von Klassendateien konzentrieren.

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


All Articles