Java 9 中的字符串连接是如何实现的?
How is String concatenation implemented in Java 9?
如JEP 280: Indify String Concatenation中所写:
Change the static String
-concatenation bytecode sequence generated by javac
to use invokedynamic
calls to JDK library functions. This will enable future optimizations of String
concatenation without requiring further changes to the bytecode emmited by javac
.
这里我想了解一下invokedynamic
调用的用法以及字节码拼接与invokedynamic
有何不同?
"old"方式输出一堆StringBuilder
面向的操作。考虑这个程序:
public class Example {
public static void main(String[] args)
{
String result = args[0] + "-" + args[1] + "-" + args[2];
System.out.println(result);
}
}
如果我们使用 JDK 8 或更早版本编译它,然后使用 javap -c Example
查看字节码,我们会看到类似这样的内容:
public class Example {
public Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: aload_0
8: iconst_0
9: aaload
10: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #5 // String -
15: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_0
19: iconst_1
20: aaload
21: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: ldc #5 // String -
26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: aload_0
30: iconst_2
31: aaload
32: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: astore_1
39: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
42: aload_1
43: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: return
}
如您所见,它创建了一个 StringBuilder
并使用了 append
。这是非常低效的,因为 StringBuilder
中内置缓冲区的默认容量只有 16 个字符,而且 编译器 无法知道提前分配更多,所以它最终不得不重新分配。它也是一堆方法调用。 (请注意,JVM 有时 可以检测并重写这些调用模式以提高它们的效率。)
让我们看看 Java 9 生成了什么:
public class Example {
public Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: aload_0
1: iconst_0
2: aaload
3: aload_0
4: iconst_1
5: aaload
6: aload_0
7: iconst_2
8: aaload
9: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
14: astore_1
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_1
19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: return
}
哦,我的天,但那更短了。 :-) 它从 StringConcatFactory
对 makeConcatWithConstants
进行一次调用,在其 Javadoc:
中这样说
Methods to facilitate the creation of String concatenation methods, that can be used to efficiently concatenate a known number of arguments of known types, possibly after type adaptation and partial evaluation of arguments. These methods are typically used as bootstrap methods for invokedynamic
call sites, to support the string concatenation feature of the Java Programming Language.
在详细介绍用于优化字符串连接的 invokedynamic
实现之前,在我看来,必须先了解一下 What's invokedynamic and how do I use it?
的背景知识
The
invokedynamic
instruction simplifies and potentially improves implementations of
compilers and runtime systems for dynamic languages on the JVM. It
does this by allowing the language implementer to define custom
linkage behavior with the invokedynamic
instruction which involves
the following the below steps.
我可能会尝试通过为实现字符串连接优化而带来的更改来向您介绍这些内容。
定义 Bootstrap 方法:-
与Java9、bootstrap方法invokedynamic
调用点,主要支持字符串拼接makeConcat
and makeConcatWithConstants
were introduced with the StringConcatFactory
实现。
invokedynamic 的使用提供了一种替代 select 直到运行时的翻译策略。 StringConcatFactory
中使用的翻译策略类似于之前java版本中引入的LambdaMetafactory
。此外,问题中提到的 JEP 的目标之一是进一步扩展这些策略。
指定常量池条目:- 这些是 invokedynamic
指令的附加静态参数,除了 (1) MethodHandles.Lookup
object which is a factory for creating method handles in the context of the invokedynamic
instruction,(2) a String
object, the method name mentioned in the dynamic call site and (3) the MethodType
对象,动态调用站点的已解析类型签名。
链接时已经有链接代码。在运行时,bootstrap 方法运行并链接到执行串联的实际代码中。 它用适当的 invokestatic
调用重写了 invokedynamic
调用。 这从常量池中加载常量字符串,利用 bootstrap 方法静态参数将这些和其他常量直接传递给 bootstrap 方法调用。
使用 invokedynamic 指令:- 这为惰性链接提供了便利,通过提供 bootstrap 一次调用目标的方法,在初始调用期间。 此处优化的具体想法是用对 java.lang.invoke.StringConcatFactory
的简单 invokedynamic
调用替换整个 StringBuilder.append
舞蹈,这将接受需要串联的值。
Indify String Concatenation proposal states with an example the benchmarking of the application with Java9 where a similar method as shared by 已编译,不同实现之间的字节码差异相当明显。
我会在这里略微添加一些细节。要了解的主要部分是如何完成字符串连接是 运行时决定,不再是编译时决定 。因此它可以改变,这意味着你已经针对 java-9 编译了你的代码 一次,它可以随心所欲地改变底层实现,而不需要重新编译。
第二点是目前有6 possible strategies for concatenation of String
:
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
您可以通过参数选择其中任何一个:-Djava.lang.invoke.stringConcat
。请注意 StringBuilder
仍然是一个选项。
如JEP 280: Indify String Concatenation中所写:
Change the static
String
-concatenation bytecode sequence generated byjavac
to useinvokedynamic
calls to JDK library functions. This will enable future optimizations ofString
concatenation without requiring further changes to the bytecode emmited byjavac
.
这里我想了解一下invokedynamic
调用的用法以及字节码拼接与invokedynamic
有何不同?
"old"方式输出一堆StringBuilder
面向的操作。考虑这个程序:
public class Example {
public static void main(String[] args)
{
String result = args[0] + "-" + args[1] + "-" + args[2];
System.out.println(result);
}
}
如果我们使用 JDK 8 或更早版本编译它,然后使用 javap -c Example
查看字节码,我们会看到类似这样的内容:
public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: aload_0 8: iconst_0 9: aaload 10: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 13: ldc #5 // String - 15: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 18: aload_0 19: iconst_1 20: aaload 21: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: ldc #5 // String - 26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 29: aload_0 30: iconst_2 31: aaload 32: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 35: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: astore_1 39: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 42: aload_1 43: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: return }
如您所见,它创建了一个 StringBuilder
并使用了 append
。这是非常低效的,因为 StringBuilder
中内置缓冲区的默认容量只有 16 个字符,而且 编译器 无法知道提前分配更多,所以它最终不得不重新分配。它也是一堆方法调用。 (请注意,JVM 有时 可以检测并重写这些调用模式以提高它们的效率。)
让我们看看 Java 9 生成了什么:
public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: aload_0 1: iconst_0 2: aaload 3: aload_0 4: iconst_1 5: aaload 6: aload_0 7: iconst_2 8: aaload 9: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 14: astore_1 15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 18: aload_1 19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 22: return }
哦,我的天,但那更短了。 :-) 它从 StringConcatFactory
对 makeConcatWithConstants
进行一次调用,在其 Javadoc:
Methods to facilitate the creation of String concatenation methods, that can be used to efficiently concatenate a known number of arguments of known types, possibly after type adaptation and partial evaluation of arguments. These methods are typically used as bootstrap methods for
invokedynamic
call sites, to support the string concatenation feature of the Java Programming Language.
在详细介绍用于优化字符串连接的 invokedynamic
实现之前,在我看来,必须先了解一下 What's invokedynamic and how do I use it?
The
invokedynamic
instruction simplifies and potentially improves implementations of compilers and runtime systems for dynamic languages on the JVM. It does this by allowing the language implementer to define custom linkage behavior with theinvokedynamic
instruction which involves the following the below steps.
我可能会尝试通过为实现字符串连接优化而带来的更改来向您介绍这些内容。
定义 Bootstrap 方法:- 与Java9、bootstrap方法
invokedynamic
调用点,主要支持字符串拼接makeConcat
andmakeConcatWithConstants
were introduced with theStringConcatFactory
实现。invokedynamic 的使用提供了一种替代 select 直到运行时的翻译策略。
StringConcatFactory
中使用的翻译策略类似于之前java版本中引入的LambdaMetafactory
。此外,问题中提到的 JEP 的目标之一是进一步扩展这些策略。指定常量池条目:- 这些是
invokedynamic
指令的附加静态参数,除了 (1)MethodHandles.Lookup
object which is a factory for creating method handles in the context of theinvokedynamic
instruction,(2) aString
object, the method name mentioned in the dynamic call site and (3) theMethodType
对象,动态调用站点的已解析类型签名。链接时已经有链接代码。在运行时,bootstrap 方法运行并链接到执行串联的实际代码中。 它用适当的
invokestatic
调用重写了invokedynamic
调用。 这从常量池中加载常量字符串,利用 bootstrap 方法静态参数将这些和其他常量直接传递给 bootstrap 方法调用。使用 invokedynamic 指令:- 这为惰性链接提供了便利,通过提供 bootstrap 一次调用目标的方法,在初始调用期间。 此处优化的具体想法是用对
java.lang.invoke.StringConcatFactory
的简单invokedynamic
调用替换整个StringBuilder.append
舞蹈,这将接受需要串联的值。
Indify String Concatenation proposal states with an example the benchmarking of the application with Java9 where a similar method as shared by
我会在这里略微添加一些细节。要了解的主要部分是如何完成字符串连接是 运行时决定,不再是编译时决定 。因此它可以改变,这意味着你已经针对 java-9 编译了你的代码 一次,它可以随心所欲地改变底层实现,而不需要重新编译。
第二点是目前有6 possible strategies for concatenation of String
:
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
您可以通过参数选择其中任何一个:-Djava.lang.invoke.stringConcat
。请注意 StringBuilder
仍然是一个选项。