JDK 9中对字符串的拼接做了什么
概要
在 JDK 9 中,Java 语言对字符串拼接进行了重大优化,核心是将原有基于 StringBuilder
的静态拼接逻辑,替换为基于 invokedynamic
指令的动态链接机制,利用 java.lang.invoke.StringConcatFactory
在运行时按需生成最优拼接实现。此变更由 JEP 280(Indify String Concatenation) 推动,不仅显著减少了字节码体积,提高了拼接性能,也为后续 JVM 和编译器进一步优化铺平了道路。
背景与动机
在 JDK 8 及更早版本中,Java 编译器将字符串拼接语句(如 a + b + c
)自动转换为:
new StringBuilder().append(a).append(b).append(c).toString();
这种方式虽然避免了手写 StringBuilder
,但每次拼接都要创建新的 StringBuilder
对象并多次调用 append
,在高频或大规模拼接场景下会带来不小的开销。
为解决上述痛点,JEP 280 提出**“Indify String Concatenation”**,即将拼接操作改为在编译期发出 invokedynamic
指令,运行期再根据实际参数类型和拼接复杂度动态生成最优实现,从而减小固定开销并支持后续热优化。
JEP 280 的实现机制
invokedynamic 指令替代 StringBuilder
编译器在遇到字符串拼接时,不再生成 StringBuilder
相关字节码,而是生成类似:
invokedynamic #bootstrap:StringConcatFactory.makeConcatWithConstants:…
的单一 invokedynamic
调用点(CallSite),使得拼接逻辑在首次执行时通过 StringConcatFactory
的 bootstrap 方法完成链接,并缓存生成的 MethodHandle
,后续调用直接走该 CallSite
,避免重复解析和对象创建。
StringConcatFactory
StringConcatFactory
提供了两个主要静态方法作为 bootstrap:
-
makeConcat(MethodHandles.Lookup, String, MethodType)
-
makeConcatWithConstants(MethodHandles.Lookup, String, MethodType, String recipe, Object… constants)
其中,makeConcatWithConstants
支持将常量内联到拼接模板中,例如 "Hello, \u0001!"
模式下常量部分在 bootstrap 时已固定,运行期只需要拼接变量,大幅减少动态分支和常量池访问开销。
拼接 Recipe
编译器为每条拼接语句生成拼接 Recipe,如
"\u0001\u0002\u0003"
其中 \u0001
, \u0002
等占位符对应不同的变量参数类型,StringConcatFactory
根据 Recipe 和参数类型生成最优字节码,如直接调用 String::concat
或专用拼接模板,从而在性能和字节码体积上均优于原始方案。
性能与优点
-
更小的字节码体积
一条invokedynamic
指令远比多条StringBuilder
链式调用生成的字节码要简洁,从而降低类文件大小并加快类加载速度。 -
运行期热优化
由于拼接逻辑通过MethodHandle
间接调用,JVM JIT 可以对其进行进一步内联或其他高级优化,适配不同参数组合的最优实现路径。 -
未来可扩展性
后续 JDK 可以在StringConcatFactory
中引入更多拼接策略(如使用String.join
、Arrays.stream().collect
等),无需重新编译用户代码即可生效,增强了拼接机制的演进能力。
编译器与工具的支持
-
javac
JDK 9+ 默认在javac
中启用 JEP 280;可通过-XDstringConcat=inline
关闭此优化,回退到老的StringBuilder
方案。 -
IDE 与第三方工具
IntelliJ IDEA、Eclipse 等 IDE 已更新对invokedynamic
拼接的字节码提示,避免误以为拼接效率低下;同时,ProGuard 和 DexGuard 等混淆/降级工具也支持对 indified 拼接的回溯处理,确保兼容性。
示例对比
代码 | JDK 8 字节码 | JDK 9+ 字节码 |
---|---|---|
String s = a + b; | new StringBuilder; sb.append(a); sb.append(b); sb.toString(); | invokedynamic makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; |
结论
JDK 9 通过 JEP 280 将字符串拼接优化为基于 invokedynamic
和 StringConcatFactory
的动态链接,不仅提升了拼接性能、减少字节码体积,还为未来拼接策略演进提供了灵活的扩展点。开发者在大多数场景下无需再手动使用 StringBuilder
,可以更直观地使用 +
操作符,而由 JVM 在幕后完成最优拼接。