字符串串联是如何在 Java9中实现的?

正如 < em > JEP 280: 指定字符串串联 中所写:

更改由 javac生成的静态 String连接字节码序列,使用对 JDK 库函数的 invokedynamic调用。这将支持未来对 String连接的优化,而不需要对 javac发出的字节码做进一步的更改。

这里我想了解 invokedynamic调用的用途,以及字节码连接与 invokedynamic有什么不同?

12563 次浏览

“旧”方法输出一系列面向 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 可以 有时候检测和重写这些调用模式,以提高它们的效率。)

让我们看看 Java9生成了什么:

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打一个电话,StringConcatFactory在 Javadoc 中说:

方法,以促进创建字符串连接方法,这些方法可用于有效地连接已知类型的已知数量的参数,可能是在对参数进行类型适应和部分计算之后。这些方法通常用作 invokedynamic调用站点的 自举方法自举方法,以支持 Java 编程语言的 字符串串联字符串串联特性。

在详细介绍用于优化字符串连接的 invokedynamic实现之前,我认为必须了解一些关于 什么是 invokedDynamic? 我如何使用它?的背景知识

invokedynamic 指令简化并可能改进 compilers and runtime systems for dynamic languages on the JVM. It 这是通过允许语言实现者定义自定义 linkage behavior with the invokedynamic instruction which involves 以下步骤。


我可能会尝试通过实现 String 连接优化所带来的更改来完成这些工作。

  • Defining the Bootstrap Method:- 对于 Java9,invokedynamic调用站点的引导方法 to support the string concatenation primarily ABC1 and ABC2 were introduced with the StringConcatFactory implementation.

    InvokedDynamic 的使用为在运行时之前选择翻译策略提供了一种替代方法。StringConcatFactory中使用的翻译策略类似于前一个 Java 版本中引入的 一个 href = “ http://download.java.net/java/jdk9/docs/api/java/lang/call/LambdaMetafactory.html”rel = “ noReferrer”> LambdaMetafactory 。此外,问题中提到的 JEP 的目标之一是进一步扩展这些策略。

  • 指定 Constant Pool Entry :-这些是 invokedynamic指令的附加静态参数,而不是(1) MethodHandles.Lookup对象,它是在 invokedynamic指令的上下文中创建方法句柄的工厂,(2) String对象,动态调用站点中提到的方法名称,(3) MethodType对象,动态调用站点的解析类型签名。

    There are already linked during the linkage of the code. At runtime, 自助法在实际代码中运行并链接进行连接。 It rewrites the invokedynamic call with an appropriate invokestatic call. This loads the constant string from the constant pool, the bootstrap method static args are leveraged to pass these and other constants straight to the bootstrap method call.

  • 通过使用 invokedDynamic 指令 :-在初始调用期间提供一次引导调用目标的方法,从而为延迟链接提供了便利。这里优化的具体想法是用一个简单的 invokedynamicjava.lang.invoke.StringConcatFactory的调用来替换整个 StringBuilder.append舞蹈,这将接受需要连接的值。

指定字符串连接提案通过一个例子说明了用 Java9对应用程序进行基准测试,其中编译了一个与 @ T. J. Crowder共享的类似方法,不同实现之间的字节码差异相当明显。

我会在这里稍微添加一些细节。要获得的主要部分是字符串串联的方式是 运行时决策,不再是编译时决策。因此,它可以改变,这意味着您已经编译了代码 once against 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选择其中的任何一个。