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
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ÀhlenDie 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:
public static E[] values(); 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.