在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实现引入makeConcatmakeConcatWithConstants

    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仍然是一个选项。