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
}

哦,我的天,但那更短了。 :-) 它从 StringConcatFactorymakeConcatWithConstants 进行一次调用,在其 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 仍然是一个选项。