正如JEP 280所述:
更改由
String
生成的静态javac
- 连接字节码序列,以使用invokedynamic
调用JDK库函数。这将使String
级联的未来优化成为可能,而无需对javac
所发送的字节码进行进一步更改。
在这里,我想了解invokedynamic
调用的用途以及字节码连接与invokedynamic
的不同之处?
答案 0 :(得分:91)
“旧”方式输出一组面向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中说明这一点:
促进字符串连接方法创建的方法,可用于有效地连接已知类型的已知类型的参数,可能在类型调整和参数的部分评估之后。这些方法通常用作
invokedynamic
调用站点的引导方法,以支持Java编程语言的字符串连接功能。
答案 1 :(得分:20)
在进入用于优化字符串连接的invokedynamic
实现的详细信息之前,我认为必须获得What's invokedynamic and how do I use it?
在 的
invokedynamic
强> 指令简化并可能改进的实现 JVM上的动态语言的编译器和运行时系统。它 这是通过允许语言实现者定义自定义来实现的 涉及invokedynamic
指令的链接行为 以下步骤。
我可能会尝试使用为实现字符串连接优化而带来的更改来完成这些操作。
定义Bootstrap方法: -
使用Java9,invokedynamic
调用网站的引导方法,主要支持字符串连接makeConcat
和makeConcatWithConstants
,并引入了 StringConcatFactory
实施。
使用invokedynamic提供了一种在运行时选择转换策略的替代方法。 StringConcatFactory
中使用的翻译策略类似于之前java版本中引入的 LambdaMetafactory
。此外,问题中提到的JEP的目标之一是进一步扩展这些策略。
指定常量池条目: - 这些是除{1} MethodHandles.Lookup
对象之外的invokedynamic
指令的附加静态参数,该对象是用于创建的工厂方法在invokedynamic
指令的上下文中处理,(2)一个String
对象,动态调用站点中提到的方法名称和(3)MethodType
对象,已解析的类型签名动态呼叫网站。
在链接代码期间已经链接了。在运行时,引导方法运行并在实际代码中进行串联链接。它使用适当的invokedynamic
调用重写invokestatic
调用。 这将从常量池中加载常量字符串,利用bootstrap方法static args将这些和其他常量直接传递给bootstrap方法调用。
使用invokedynamic指令: - 通过在初始调用期间提供一次引导调用目标的方法,它为延迟链接提供了便利。 此处优化的具体想法是使用对StringBuilder.append
的简单invokedynamic
调用替换整个java.lang.invoke.StringConcatFactory
舞蹈,这将接受需要连接的值。
Indify String Concatenation提案通过示例说明了使用Java9对应用程序进行基准测试,其中编译了@T.J. Crowder共享的类似方法,并且字节码的差异在不同的实现之间相当明显。 / p>
答案 2 :(得分:18)
我在这里稍微添加一些细节。要获得的主要部分是字符串连接的完成方式是运行时决策,而不是编译时间。因此它可以改变,这意味着您已经编译了代码一次针对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
仍然是一个选项。