是否可以禁用javac的静态最终变量内联?

sjl*_*lee 53 java dependencies bytecode javac

Java静态编译器(javac)内联一些静态最终变量,并将值直接带到常量池.请考虑以下示例.A类定义了一些常量(公共静态最终变量):

public class A {
    public static final int INT_VALUE = 1000;
    public static final String STRING_VALUE = "foo";
}
Run Code Online (Sandbox Code Playgroud)

B类使用以下常量:

public class B {
    public static void main(String[] args) {
        int i = A.INT_VALUE;
        System.out.println(i);
        String s = A.STRING_VALUE;
        System.out.println(s);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译类B时,javac从类A获取这些常量的值,并在B.class中内联这些值.结果,编译时必须从A类中删除的依赖关系B从字节码中删除.这是一种相当奇特的行为,因为您在编译时正在烘焙这些常量的值.你会认为这是JIT编译器在运行时可以做的最容易的事情之一.

是否有任何方法或任何隐藏的编译器选项可以禁用javac的这种内联行为?对于后台,我们正在考虑进行字节码分析以实现依赖性目的,并且它是字节码分析无法检测编译时依赖性的少数情况之一.谢谢!

编辑:这是一个棘手的问题,因为通常我们不控制所有源(例如,定义常量的第三方库).我们有兴趣从使用常量的角度检测这些依赖关系.由于引用是从使用常量的代码中删除的,因此没有简单的方法来检测它们,缺少源代码分析.

DJC*_*rth 44

Java Puzzlers(Joshua Bloch)的第93项说,你可以通过防止最终值被视为常数来解决这个问题.例如:

public class A {
  public static final int INT_VALUE = Integer.valueOf(1000).intValue();
  public static final String STRING_VALUE = "foo".toString();
}
Run Code Online (Sandbox Code Playgroud)

当然,如果您无法访问定义常量的代码,那么这些都不相关.

  • 无法在编译时禁用此优化是"javac"削弱创建适当调试器的能力的众多方法之一. (3认同)
  • 我记得`System.out`的赋值是这样的:`public final static PrintStream out = nullPrintStream();`(实际的流稍后由本机代码设置).如果`currentTimeMillis()> 0`,`nullPrintStream()`返回null.从来没有打过我为什么这样做(而不是只是`... out = null;`).当然如果我打扰阅读`nullInputStream()`的文档,那就明白了...... (2认同)

Jon*_*eet 13

我不相信.最简单的解决方法是将这些作为属性而不是字段公开:

public class A {
    private static final int INT_VALUE = 1000;
    private static final String STRING_VALUE = "foo";

    public static int getIntValue() {
        return INT_VALUE;
    }
    public static String getStringValue() {
        return STRING_VALUE;
    }
}
Run Code Online (Sandbox Code Playgroud)

不要忘记,在某些情况下,内联对于值的使用至关重要 - 例如,如果您要INT_VALUE在开关块中用作案例,则必须将其指定为常量值.

  • @Tim:我的直觉是函数只能在运行时内联.否则,您创建的任何存根方法(`return null;`)都需要您在实现该方法后重新编译所有使用它的类. (3认同)

Tom*_*ine 9

要停止内联,您需要使值为非编译时常量(JLS术语).您可以在不使用函数的情况下执行此操作,并通过null在初始化表达式中使用a 来创建最少的字节码.

public static final int INT_VALUE = null!=null?0: 1000;
Run Code Online (Sandbox Code Playgroud)

虽然它在代码生成方面非常直观,但javac应优化它是推送一个立即整数,然后存储到静态初始化器中的静态字段.


Mar*_*ers 7

JLS 13.4.9处理此问题.他们的建议是,如果值以任何可能的方式改变,基本上避免编译时常量.

(需要内联常量的一个原因是switch语句在每种情况下都需要常量,并且没有两个这样的常量值可能是相同的.编译器在编译时检查switch语句中的重复常量值;类文件格式不做案例值的象征性联系.)

在广泛分布的代码中避免"不稳定常量"问题的最好方法是将编译时常量声明为仅真正不可能改变的值.除了真正的数学常量之外,我们建议源代码非常节省地使用声明为static和final的类变量.如果需要final的只读性质,更好的选择是声明私有静态变量和合适的访问器方法来获取其值.因此我们建议:

private static int N;
public static int getN() { return N; }
Run Code Online (Sandbox Code Playgroud)

而不是:

public static final int N = ...;
Run Code Online (Sandbox Code Playgroud)

没有问题:

public static int N = ...;
Run Code Online (Sandbox Code Playgroud)

如果N不必是只读的.