【问题标题】:Does the JLS require inlining of final String constants?JLS 是否需要内联最终字符串常量?
【发布时间】:2017-01-16 02:17:59
【问题描述】:

我在处理一些字节码时遇到了一个问题,其中某个 final String 常量没有被 Java 编译器 (Java 8) 内联,请参见下面的示例:

public class MyTest
{
  private static final String ENABLED  = "Y";
  private static final String DISABLED = "N";

  private static boolean isEnabled(String key) {
      return key.equals("A");
  }

  private static String getString(String key, String value) {
      return key + value;
  }

  public static void main(String[] args) throws Exception {
    String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag);

    String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag2);
  }
}

使用 javac (1.8.0_101) 生成的字节码

public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #8                  // String F
         2: ldc           #2                  // String A
         4: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
         7: ifeq          16
        10: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        13: goto          19
        16: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        19: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        22: astore_1
        23: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc           #8                  // String F
        32: ldc           #2                  // String A
        34: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
        37: ifeq          46
        40: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        43: goto          49
        46: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        49: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        52: astore_2
        53: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        56: aload_2
        57: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: return

您可以看到第二次访问字段ENABLEDDISABLED 时,编译器没有内联它们的值(使用ldc),而是使用getstatic 直接访问该字段。使用其他编译器(Java 7、Eclipse)对其进行测试并没有触发相同的行为,并且常量始终是内联的。

这可以被认为是一个编译器错误,还是根据 JLS,它是否允许一直内联字符串常量?

【问题讨论】:

  • 定义“内联”。 JLS 需要pooling。你说的是这个吗?
  • 内联意味着用直接将常量值压入堆栈的指令替换getstatic指令,例如ldc 在字符串的情况下,或 iconst_X/bipush/sipush/... 在整数基元的情况下,...
  • @piet.t:我删除了字节码标签,因为语言规范确实不处理字节码。但是,行为的定义没有歧义,可以回答问题。
  • 很好看。这确实是一个错误,它已通过对 JDK-8066871 的修复得到修复,该修复程序集成在 JDK 9 中并向后移植到 JDK 1.8.0_102——即 1.8.0_101 加上一个补丁集。请注意,JDK-8066871 有不同的症状,但相同的修复程序可以纠正该错误以及此处描述的错误。
  • 我刚刚提交了一份错误报告 (JI-9043578),因为我没有在错误数据库中找到相关条目。我想它可以关闭。

标签: java java-8 language-lawyer javac jls


【解决方案1】:

是的,规范要求“内联”行为:

13.1. The Form of a Binary

  1. 对作为常量变量的字段(第 4.12.4 节)的引用必须在编译时解析为由常量变量的初始化程序表示的值 V

    如果这样的字段是static,那么二进制文件的代码中不应存在对该字段的引用,包括声明该字段的类或接口。此类字段必须始终显示为已初始化(第 12.4.2 节);不得观察该字段的默认初始值(如果不同于 V)。

    如果这样的字段不是static,则在二进制文件的代码中不应出现对该字段的引用,除非在包含该字段的类中。 (它将是一个类而不是一个接口,因为一个接口只有 static 字段。)该类应该具有在实例创建期间将字段的值设置为 V 的代码(第 12.5 节)。

注意,这如何准确地解决您的场景:“如果这样的字段是 static,那么在二进制文件的代码中不应存在对该字段的引用,包括声明字段”。

换句话说,如果你遇到一个不遵守这个的编译器,你就发现了一个编译器错误。


作为附录,查找此信息的起点是:

4.12.4. final Variables

常量变量是基本类型的final 变量或String 类型的变量,它使用常量表达式(§15.28) 进行初始化。变量是否为常量变量可能会影响类初始化(§12.4.1)、二进制兼容性(§13.1§13.4.9)和明确赋值(§16 (Definite Assignment))。

【讨论】:

  • 非常感谢,这正是我一直在寻找的,所以这证实了观察到的行为确实是一个编译器错误。
  • 我检查了所有这些引用,除了二进制兼容性:)
猜你喜欢
  • 1970-01-01
  • 2011-03-03
  • 2020-09-28
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 2012-11-11
  • 2011-04-27
  • 1970-01-01
相关资源
最近更新 更多