JDK 9 / JEP 280: penggabungan string tidak akan pernah sama lagi

Halo lagi. Seperti yang sudah kami tulis, minggu depan grup pelatihan baru tentang kursus "Pengembang Java" akan dimulai, sesuai dengan tradisi yang sudah ada, kami akan membagikan kepada Anda terjemahan materi yang menarik tentang topik tersebut.

Dimulai dengan JDK 9, rangkaian string telah mengalami perubahan signifikan.

JEP 280 ("Indify String Concatenation") diimplementasikan sebagai bagian dari JDK 9 dan, sesuai dengan bagian "Ringkasan": "Mengubah javac yang dihasilkan rangkaian string bytecode urutan untuk menggunakan panggilan invokedynamic ke fungsi perpustakaan JDK." Efek ini memiliki pada penggabungan string di Jawa paling mudah diperhatikan dengan melihat output javap dari kelas yang menggunakan penggabungan string yang dikompilasi dalam JDK sebelum JDK 9 dan setelah JDK 9.



Untuk demonstrasi pertama, kelas Java sederhana yang disebut "HelloWorldStringConcat" akan digunakan.

package dustin.examples; import static java.lang.System.out; public class HelloWorldStringConcat { public static void main(final String[] arguments) { out.println("Hello, " + arguments[0]); } } 

Berikut ini adalah perbandingan perbedaan untuk -vote javap output untuk metode main(String) dari kelas HelloWorldStringConcat ketika dikompilasi dengan JDK 8 (AdoptOpenJDK) dan JDK 11 (Oracle OpenJDK) . Saya menyoroti beberapa perbedaan utama.

JDK 8 output javap

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class Last modified Jan 28, 2019; size 625 bytes MD5 checksum 3e270bafc795b47dbc2d42a41c8956af Compiled from "HelloWorldStringConcat.java" public class dustin.examples.HelloWorldStringConcat minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload ":()V 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return //  java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

JDK 11 output javap

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class Last modified Jan 28, 2019; size 908 bytes MD5 checksum 0e20fe09f6967ba96124abca10d3e36d Compiled from "HelloWorldStringConcat.java" public class dustin.examples.HelloWorldStringConcat minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: iconst_0 5: aaload 6: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String; 11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: return 

Bagian "Deskripsi" di JEP 280 menjelaskan perbedaan ini: "Idenya adalah untuk mengganti seluruh tarian gabungan StringBuilder dengan panggilan invokedynamic sederhana ke java.lang.invoke.StringConcatFactory , yang akan mengambil nilai yang perlu digabungkan." Bagian yang sama menunjukkan perbandingan yang sama dari output yang dikompilasi untuk contoh penggabungan string yang sama.

Output yang dikompilasi dengan JDK 11 untuk penggabungan mudah tidak hanya lebih sedikit dari output dari JDK 8; ia juga memiliki lebih sedikit operasi "mahal". Peningkatan kinerja potensial dapat dicapai karena fakta bahwa tidak perlu membungkus tipe primitif dan Anda tidak perlu membuat banyak objek tambahan. Salah satu motif utama untuk perubahan ini adalah “untuk meletakkan fondasi untuk membuat penangan string yang dioptimalkan yang diimplementasikan tanpa perlu mengubah kompiler Java-to-bytecode” dan “memungkinkan optimasi penggabungan string di masa depan tanpa perubahan tambahan pada bytecode yang dihasilkan oleh javac. "

Ada konsekuensi yang menarik dari ini dalam hal menggunakan StringBuffer (yang menurut saya sulit untuk menemukan penggunaan yang baik ) dan StringBuilder . Di JEP 280, "Non-Goal" menyatakan tidak untuk "memperkenalkan API baru untuk String dan / atau StringBuilder yang dapat membantu menciptakan strategi terjemahan yang lebih efisien." Dalam hal ini, untuk penggabungan string sederhana, seperti pada contoh di awal tulisan ini, penggunaan eksplisit StringBuilder dan StringBuffer secara virtual mengecualikan kompiler dari menggunakan fitur yang diperkenalkan pada JEP 280 , yang akan kita bahas dalam posting ini.

Dua daftar berikut ini menunjukkan implementasi serupa dari aplikasi sederhana yang ditunjukkan di atas, tetapi alih-alih merangkai string, mereka menggunakan StringBuilder dan StringBuffer secara berurutan. Ketika javap -verbose dieksekusi untuk kelas-kelas ini setelah mereka dikompilasi dengan JDK 8 dan dengan JDK 11, tidak ada perbedaan signifikan dalam metode utama (String []).

Penggunaan eksplisit StringBuilder di JDK 8 dan JDK 11 adalah sama

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringBuilder { public static void main(final String[] arguments) { out.println(new StringBuilder().append("Hello, ").append(arguments[0]).toString()); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class Last modified Jan 28, 2019; size 627 bytes MD5 checksum e7acc3bf0ff5220ba5142aed7a34070f Compiled from "HelloWorldStringBuilder.java" public class dustin.examples.HelloWorldStringBuilder minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; //  java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder //  java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class Last modified Jan 28, 2019; size 627 bytes MD5 checksum d04ee3735ce98eb6237885fac86620b4 Compiled from "HelloWorldStringBuilder.java" public class dustin.examples.HelloWorldStringBuilder minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

Penggunaan eksplisit StringBuffer di JDK 8 dan JDK 11 adalah sama

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringBuffer { public static void main(final String[] arguments) { out.println(new StringBuffer().append("Hello, ").append(arguments[0]).toString()); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class Last modified Jan 28, 2019; size 623 bytes MD5 checksum fdfb90497db6a3494289f2866b9a3a8b Compiled from "HelloWorldStringBuffer.java" public class dustin.examples.HelloWorldStringBuffer minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuffer 6: dup 7: invokespecial #4 // Method java/lang/StringBuffer."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: invokevirtual #7 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class Last modified Jan 28, 2019; size 623 bytes MD5 checksum e4a83b6bb799fd5478a65bc43e9af437 Compiled from "HelloWorldStringBuffer.java" public class dustin.examples.HelloWorldStringBuffer minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuffer 6: dup 7: invokespecial #4 // Method java/lang/StringBuffer."":()V 10: ldc #5 // String Hello, 12: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 15: aload_0 16: iconst_0 17: aaload 18: invokevirtual #6 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: invokevirtual #7 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 

JDK 8 dan JDK 11 Menangani Rangkaian String Bertali

Untuk contoh terakhir dari perubahan pada JEP 280 dalam aksi, saya menggunakan kode sampel yang dapat memecah kerentanan beberapa pengembang Java dan merangkai string dalam satu lingkaran. Perlu diingat bahwa ini hanya contoh ilustratif, dan semuanya akan baik-baik saja, tetapi jangan mencoba untuk mengulanginya di rumah.

 package dustin.examples; import static java.lang.System.out; public class HelloWorldStringConcatComplex { public static void main(final String[] arguments) { String message = "Hello"; for (int i=0; i<25; i++) { message += i; } out.println(message); } } 

 Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class Last modified Jan 30, 2019; size 766 bytes MD5 checksum 772c4a283c812d49451b5b756aef55f1 Compiled from "HelloWorldStringConcatComplex.java" public class dustin.examples.HelloWorldStringConcatComplex minor version: 0 major version: 52 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String Hello 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 25 8: if_icmpge 36 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: iload_2 23: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 26: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: astore_1 30: iinc 2, 1 ":()V 18: aload_1 33: goto 5 36: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 39: aload_1 40: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 43: return 

  Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class Last modified Jan 30, 2019; size 1018 bytes MD5 checksum 967fef3e7625965ef060a831edb2a874 Compiled from "HelloWorldStringConcatComplex.java" public class dustin.examples.HelloWorldStringConcatComplex minor version: 0 major version: 55 . . . public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String Hello 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 25 8: if_icmpge 25 11: aload_1 12: iload_2 13: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String; 18: astore_1 19: iinc 2, 1 22: goto 5 25: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_1 29: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: return 

Dalam presentasi “Cukup java.lang. Berusaha Menggantung Diri Sendiri ...” , Dr. Heinz M. Kabutz dan Dmitry Vyazelenko membahas perubahan yang dibuat pada rangkaian string Jawa dan merangkumnya secara singkat, “+ lagi tidak dikompilasi ke dalam StringBuilder ”. Pada slide Lessons from Today, mereka menyatakan: "Gunakan + alih-alih StringBuilder jika memungkinkan" dan "Kompilasi ulang kelas untuk Java 9+."

Perubahan yang diterapkan di JDK 9 dengan JEP 280 , "akan memungkinkan di masa depan untuk mengoptimalkan penggabungan string, tanpa memerlukan perubahan tambahan dalam bytecode yang dihasilkan oleh javac." Menariknya, baru-baru ini diumumkan bahwa JEP 348 ("Java Compiler Intrinsics untuk JDK APIs") sekarang menjadi kandidat untuk JEP, dan tujuannya adalah menggunakan pendekatan yang serupa untuk mengkompilasi metode String :: format dan Objects::hash .

Menurut Anda, apa artikel yang bermanfaat? Kami menunggu komentar Anda dan mengundang semua orang ke hari terbuka di kursus Java Developer, yang akan diadakan pada tanggal 25 Maret oleh direktur jenderal OTUS - Vitaly Chibrikov .

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


All Articles