如何关闭字符串连接优化

How to turn off string concatenation optimization

In Java 9 Oracle 改进了字符串连接。现在 "" + someBoolean 变成 invokedynamicStringConcatFabric.makeConcat 作为 bootstrap 方法。该结构在运行时生成 类 以连接您的字符串。我想禁用此行为并回退到普通的旧字符串生成器。
所以我认为 javac 有标志可以做我想做的事。但我找不到它。

字符串连接功能有两个部分。

  1. 在运行时间

    在 Java 9+ 中,在 运行 时,字符串连接由 StringConcatFactory class (javadoc) 控制。这是因为 javac 在需要字符串连接的地方生成 invokedynamic 字节码到 StringConcatFactory::makeConcat

    StringConcatFactoryStrategy 枚举 (source code) 的形式为 运行time 连接定义了几种策略。

    您可以通过设置 -Djava.lang.invoke.stringConcat

    从命令行更改默认策略

    要在运行时获得Java-8行为,您需要将其设置为BC_SB,代表"Bytecode, StringBuilder"

    为了完整起见,以下是其他值:

    /**
     * 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
    
  2. 编译时

    正如 Kayaman 正确指出的那样,StringConcatFactory 仅在 运行 时间 影响程序。在连接字符串的任何地方,字节码仍将包含 invokedynamicStringConcatFactory。有几种方法可以返回对 StringBuilder:

    的调用
    • 禁用此行为的最直接方法是将 --release=8 标志传递给 javac 以强制生成 Java-8 兼容代码。但是,这不仅会影响字符串连接。

    • 更有针对性的选项是通过传递 -XDstringConcat=inline.

      来具体控制串联

      我们以这段代码为例:

      public class Print {    
          public static void main(String[] args) {
              String foo = "a";
              String bar = "b";
              System.out.println(foo+bar);
          }
      }
      

      如果我们在没有任何标志的情况下编译它,我们将得到:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: aload_1
            10: aload_2
            11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
            16: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            19: return
      }
      

      注意 invokedynamicmakeConcatWithConstants

      但是,如果我们 运行 javac -XDstringConcat=inline Print.java,我们会得到:

      public class Print {
        public Print();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            30: return
      }
      

      此处 String 连接是使用 StringBuilder 完成的,就像 Java 8.

从 Java 15 开始,StringConcatFactory 除了 MH_INLINE_SIZED_EXACT 之外没有提供其他串联策略。有关详细信息,请参阅 this thread in the mailing lists and this bug

正如 Malt 在 中指出的那样,您现在只能通过在编译时禁用 invokedynamic 来返回到 StringBuilders。为此,将 -XDstringConcat=inline 标志传递给编译器。上面引用的 Malt 的回答中有更多详细信息。

如果使用来自 Maven Central 的 JAR 文件,you will need to recompile them yourself