📜 ⬆️ ⬇️

JDK 9 / JEP 280: string concatenation will never be the same again

Hello again. As we have already written, next week a new Java Developer training group will start, according to a well-established tradition, we are sharing with you a translation of interesting material on the topic.

Since JDK 9, string concatenation has undergone significant changes.

JEP 280 (“Indify String Concatenation”) was implemented within JDK 9 and, in accordance with the Summary section: “Changes the static string concatenation byte-code generated by javac to use invokedynamic calls to JDK library functions.” The effect this has on string concatenation in Java is easiest to notice by looking at the javap output of classes that use string concatenation, which are compiled into the JDK before JDK 9 and after JDK 9.
')


For the first demonstration, you will use a simple Java class called "HelloWorldStringConcat".

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

Below is a mapping of the differences for the -verbose javap output for the main(String) method of the HelloWorldStringConcat class when compiled with JDK 8 (AdoptOpenJDK) and JDK 11 (Oracle OpenJDK) . I highlighted a few key differences.

JDK 8 javap output

 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 javap output

 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 

The Description section in JEP 280 describes this distinction: “The idea is to replace the entire StringBuilder attachment dance with a simple invokedynamic call to java.lang.invoke.StringConcatFactory , which will take values ​​that need to be merged.” The same section shows a similar comparison of the compiled output for a similar string concatenation example.

Compiled output from JDK 11 for simple concatenation is not just a smaller number of lines than in output from JDK 8; he also has fewer “expensive” operations. Potential performance improvements can be achieved due to the fact that there is no need to wrap primitive types and do not need to create many additional objects. One of the main reasons for this change was “to lay the groundwork for creating optimized string concatenation handlers, implemented without the need to change the Java-to-bytecode compiler” and “include future optimizations of string concatenation without additional changes to the javac generated bytecode. "

There is an interesting consequence of this in terms of using a StringBuffer (which is difficult for me to find good use anyway ) and StringBuilder . In JEP 280, “Non-Goal” stated not to “introduce any new APIs for String and / or StringBuilder that could help create more efficient translation strategies.” In this regard, for simple string concatenation, such as in the example at the beginning of this post, the explicit use of StringBuilder and StringBuffer effectively excludes the compiler from using the feature introduced in JEP 280 , which we discuss in this post.

The following two listings show similar implementations of the simple application shown above, but instead of string concatenation, they use StringBuilder and StringBuffer, respectively. When javap -verbose is executed for these classes after they are compiled with JDK 8 and with JDK 11, there are no significant differences in the main (String []) methods.

Explicit use of StringBuilder in JDK 8 and JDK 11 is the same

 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 

Explicit use of StringBuffer in JDK 8 and JDK 11 is the same

 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 and JDK 11 Handling looped string concatenations

For the last example of the changes in JEP 280 in action, I use sample code that can break the susceptibility of some Java developers and perform string concatenation in a loop. Keep in mind that this is only an illustrative example, and everything will be fine, but do not try to repeat it at home.

 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 

In the presentation “Enough java.lang.String to Hang Ourselves ...” , Dr. Heinz M. Kabutz (Heinz M. Kabutz) and Dmitry Vyazelenko (Dmitry Vyazelenko) discuss the changes made to the concatenation of Java strings and briefly summarize them, “+ more does not compile to StringBuilder. ” On the "Lessons from Today" slide, they state: "Use + instead of StringBuilder, where possible" and "Recompile classes for Java 9+."

The changes implemented in JDK 9 with JEP 280 “will allow in the future to optimize string concatenation without requiring additional changes in the bytecode generated by javac.” Interestingly, it was recently announced that JEP 348 ("Java Compiler Intrinsics for JDK APIs") is now a JEP candidate, and its goal is to use a similar approach to compile the String :: format and Objects::hash methods.

What do you think useful article? We are looking forward to your comments and invite everyone to the open door of the Java Developer course, which Vitaly Chibrikov , General Manager of OTUS will conduct on March 25th.

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


All Articles