在Java 9中如何实现string连接?
正如JEP 280所写:
将由
javac
生成的静态String
-concatenation字节码序列更改为对JDK库函数使用invokedynamic
调用。 这将使未来优化String
连接,而不需要进一步修改由javac
提供的字节码。
在这里,我想了解invokedynamic
调用的用法,以及字节码级联与invokedynamic
不同之处是什么?
“老”的方式输出一堆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 Example(); 码: 0:aload_0 1:invokespecial#1 //方法java / lang / Object。“<init>”:()V 4:回报 public static void main(java.lang.String []); 码: 0:new#2 // class java / lang / StringBuilder 3:dup 4:invokespecial#3 //方法java / lang / StringBuilder。“<init>”:()V 7:aload_0 8:iconst_0 9:aaload 10:invokevirtual#4 //方法java / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder; 13:ldc#5 // String - 15:invokevirtual#4 //方法java / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder; 18:aload_0 19:iconst_1 20:aaload 21:invokevirtual#4 //方法java / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder; 24:ldc#5 // String - 26:invokevirtual#4 //方法java / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder; 29:aload_0 30:iconst_2 31:aaload 32:invokevirtual#4 //方法java / lang / StringBuilder.append:(Ljava / lang / String;)Ljava / lang / StringBuilder; 35:invokevirtual#6 //方法java / lang / StringBuilder.toString :()Ljava / lang / String; 38:astore_1 39:getstatic#7 //字段java / lang / System.out:Ljava / io / PrintStream; 42:aload_1 43:invokevirtual#8 //方法java / io / PrintStream.println:(Ljava / lang / String;)V 46:回报 }
如你所见,它创build一个StringBuilder
并使用append
。 由于StringBuilder
中的内置缓冲区的默认容量只有16个字符, 编译器无法预先分配更多的资源,因此这种方法的效率相当低,因此最终不得不重新分配。 这也是一堆方法调用。 (请注意,JVM 有时可以检测并重写这些调用模式,以使它们更高效。
我们来看看Java 9生成的内容:
公共类例子{ public Example(); 码: 0:aload_0 1:invokespecial#1 //方法java / lang / Object。“<init>”:()V 4:回报 public static void main(java.lang.String []); 码: 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 //字段java / lang / System.out:Ljava / io / PrintStream; 18:aload_1 19:invokevirtual#4 //方法java / io / PrintStream.println:(Ljava / lang / String;)V 22:回报 }
哦,我的,但是这个更短。 :-)它从StringConcatFactory
调用makeConcatWithConstants
,它在Javadoc中说:
有助于创buildstring连接方法的方法,这些方法可用于高效地连接已知types的已知数量的参数,可能在types自适应和参数的部分评估之后进行。 这些方法通常用作
invokedynamic
调用站点的引导方法 ,以支持Java编程语言的string连接特性。
在介绍用于优化string连接的invokedynamic
实现的细节之前,我认为必须获得关于什么是invokedynamic的一些背景信息, 以及如何使用它?
invokedynamic
指令简化并有可能改进JVM上dynamic语言的编译器和运行时系统的实现 。 它通过允许语言实现者使用invokedynamic
指令来定义自定义链接行为来实现这一点,其涉及以下步骤。
我可能会尝试通过为实现string连接优化带来的更改带领您完成这些任务。
-
定义引导方法 : – 使用Java9,
invokedynamic
调用站点的引导方法来支持string连接,主要是使用StringConcatFactory
实现引入makeConcat
和makeConcatWithConstants
。invokedynamic的使用提供了一个替代scheme来select一个翻译策略,直到运行时。 在
StringConcatFactory
使用的翻译策略与前面的java版本中引入的LambdaMetafactory
类似。 另外问题中提到的JEP的目标之一是进一步扩展这些策略。 -
指定常量池条目 : – 除了(1)
MethodHandles.Lookup
对象,它是在invokedynamic
指令的上下文中创build方法句柄的工厂,(2)一个String
对象,方法dynamic呼叫站点中提到的名称和(3)MethodType
对象,dynamic呼叫站点的parsingtypes签名。代码链接期间已经链接了。 在运行时, 引导方法运行并链接到进行并置的实际代码中。 它使用适当的
invokestatic
调用来重写invokedynamic
调用。 这从常量池加载常量string,引导方法静态参数利用这些和其他常量直接传递给引导方法调用。 -
使用invokedynamic指令 : – 通过在初始调用期间提供一次引导调用目标的方法,为懒惰链接提供便利。 这里优化的具体思路是用一个简单的
invokedynamic
调用来replace整个StringBuilder.append
舞蹈到java.lang.invoke.StringConcatFactory
,它将接受需要连接的值。
Indifystring连接提议用一个例子说明了使用Java9编写的应用程序的基准testing,其中编译了@TJ Crowder共享的类似方法,并且字节码的差异在变化的实现之间相当明显。
我会在这里稍微添加一些细节。 要获得的主要部分是如何完成string连接是一个运行时决定,而不是一个编译时间了 。 因此,它可以改变,这意味着你已经编译了你的代码一次,而不是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 }
您可以通过参数select其中的任何一个: -Djava.lang.invoke.stringConcat
。 请注意, StringBuilder
仍然是一个选项。