Jumlah nilai maksimum di enum Bagian I

Bagian Satu, Teoritis | Bagian Dua, Praktis


Berdasarkan tweet dari Evgeny Mandrikov alias godin :


Di dalamnya, ia bertanya-tanya berapa nilai maksimum yang dapat ditentukan dalam enum di Jawa. Setelah serangkaian percobaan dan penggunaan sihir hitam ConstantDynamic ( JEP 309 ), penulis pertanyaan muncul di nomor 8191.

Dalam serangkaian dua artikel, kami mencari batas teoritis jumlah elemen dalam pencacahan, mencoba untuk lebih dekat dengan mereka dalam praktik, dan mencari tahu bagaimana JEP 309 dapat membantu.

Pengintaian


Bab review di mana kita pertama kali melihat enumerasi dibongkar.

Pertama, mari kita lihat apa yang diterjemahkan oleh enumerasi berikut:

 public enum FizzBuzz { Fizz, Buzz, FizzBuzz; } 

Setelah kompilasi dan pembongkaran:

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" 


Dalam daftar kami bertemu

  • Satu bidang public static final untuk setiap nilai yang ditentukan dalam enumerasi
  • Bidang sintetis pribadi $VALUES , detail implementasi metode values()
  • Implementasi metode values() dan valueOf()
  • Konstruktor pribadi
  • Blok inisialisasi statis, di mana sebenarnya hal paling menarik terjadi. Mari kita pertimbangkan lebih terinci.

Dalam bentuk kode java, yang terakhir terlihat seperti ini:

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

Pertama, contoh elemen enumerasi dibuat. Mesin virtual yang dibuat segera ditulis ke bidang public static final sesuai.

Kemudian sebuah array dibuat dan diisi dengan tautan ke instance dari semua elemen enumerasi. Tautan diambil dari bidang kelas yang kami inisialisasi dalam paragraf di atas. Array yang diisi disimpan di bidang private static final $VALUES .

Setelah itu, daftar siap digunakan.

Bottleneck


Bab yang membosankan di mana kami mencari batasan jumlah elemen enumerasi.

Anda dapat memulai pencarian Anda dengan bab JLS ยง8.9.3 โ€œAnggota Enumโ€:

JLS 8.9.3 Anggota Enum
Anggota enum tipe E semuanya adalah sebagai berikut:
...
* Untuk setiap konstanta enum c yang dinyatakan dalam badan deklarasi E, E miliki
bidang final statis publik yang dinyatakan secara implisit dari tipe E yang memiliki hal yang sama
beri nama c. Bidang ini memiliki penginisialisasi variabel yang instantiate E dan melewati apa pun
argumen c ke konstruktor yang dipilih untuk E. Field memiliki anotasi yang sama
as c (jika ada).

Bidang-bidang ini secara implisit dinyatakan dalam urutan yang sama dengan yang sesuai
enum konstanta, sebelum bidang statis apa pun secara eksplisit dinyatakan dalam badan
deklarasi E.
...
* Metode yang dinyatakan secara implisit berikut ini:
 /** * 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); 



Jadi, setiap kelas enumerasi memiliki metode values() yang mengembalikan array dengan semua elemen yang dideklarasikan dalam enumerasi ini. Oleh karena itu, enumerasi bola dalam ruang hampa tidak dapat mengandung lebih dari elemen Integer.MAX_VALUE + 1 .

Pindah. Enumerasi di Jawa direpresentasikan sebagai turunan dari kelas java.lang.Enum , dan karenanya mereka tunduk pada semua batasan yang melekat dalam kelas di JVM.

Mari kita lihat deskripsi tingkat tinggi dari struktur file kelas yang diberikan dalam JVMS ยง4.1 "Struktur ClassFile":

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

Seperti yang sudah kita ketahui dari JLS ยง8.9.3, bidang dengan nama yang sama dibuat untuk setiap elemen enumerasi di kelas yang dihasilkan. Jumlah bidang dalam kelas mendefinisikan 16-bit unsigned fields_count , yang membatasi kita untuk 65_535 bidang dalam satu file kelas atau 65_534 elemen enumerasi. Satu bidang dicadangkan untuk array $VALUES , klon yang mengembalikan metode values() . Ini tidak secara eksplisit dinyatakan dalam spesifikasi, tetapi tidak mungkin untuk datang dengan solusi yang lebih elegan.

Nama-nama bidang, metode, kelas, nilai konstan dan banyak lagi disimpan di kumpulan konstan.
Jika Anda tidak tahu apa-apa tentang struktur internal kolam konstan, saya sarankan membaca artikel kuno dari lany . Terlepas dari kenyataan bahwa sejak ditulis di kumpulan konstanta banyak hal baru dan menarik telah muncul, prinsip-prinsip dasar tetap tidak berubah.
Ukuran kumpulan konstanta kelas juga dibatasi oleh jumlah 65_535 elemen. Kumpulan konstanta dari kelas yang dibentuk dengan benar tidak pernah kosong. Minimal, akan ada nama untuk kelas ini.

Sebagai contoh, kumpulan konstanta dari kelas enumerasi kosong yang dikompilasi oleh javac dari OpenJDK 14-ea + 29 tanpa informasi debugging berisi 29 kejadian.

Oleh karena itu jumlah elemen 65_534 dalam satu enumerasi juga tidak dapat dicapai. Dalam kasus terbaik, kita dapat mengandalkan 65_505 atau angka yang dekat dengan ini.

Akord terakhir dalam pengantar yang berlarut-larut ini:

Nilai dapat <clinit> ke bidang static final hanya di blok inisialisasi statis, yang diwakili di tingkat file kelas dengan metode yang disebut <clinit> . Bytecode metode apa pun tidak dapat menempati lebih dari 65_535 byte. Nomor yang dikenal, bukan?

Satu instruksi penulisan statis putstatic membutuhkan 3 byte, yang memberi kita perkiraan kasar 65_535 / 3 = 21_845 . Faktanya, estimasi ini dilebih-lebihkan. Instruksi mengambil nilai untuk menulis ke bidang dari atas tumpukan, yang mana salah satu instruksi sebelumnya ditempatkan di sana. Dan instruksi ini juga memakan byte yang berharga. Tetapi bahkan jika Anda tidak memperhitungkannya, jumlah yang dihasilkan masih kurang dari 65_505.

Singkatnya:

  • Format file kelas membatasi jumlah maksimum elemen enumerasi sekitar 65_505
  • Mekanisme inisialisasi bidang akhir statis membatasi kita bahkan lebih. Secara teoritis - hingga 21_845 elemen maksimum, dalam praktiknya jumlah ini bahkan lebih sedikit

Dalam artikel terakhir dari seri ini, kami akan fokus pada optimasi yang tidak sehat dan pembuatan file kelas.

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


All Articles