is it possible to disable javac's inlining of

2019-01-05 01:54发布

The Java static compiler (javac) inlines some static final variables and brings the values directly to the constant pool. Consider the following example. Class A defines some constants (public static final variables):

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

Class B uses these constants:

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);
    }
}

When you compile class B, javac gets the values of these constants from class A and inlines these values in B.class. As a result, the dependency B had to class A at the compile time is erased from the bytecode. This is a rather peculiar behavior because you are baking in the values of these constants at the time of compilation. And you would think that this is one of the easiest things that the JIT compiler can do at runtime.

Is there any way or any hidden compiler option that lets you disable this inlining behavior of javac? For the background, we're looking into doing bytecode analysis for dependency purposes, and it is one of the few cases where bytecode analysis fails to detect compile-time dependencies. Thanks!

Edit: this is a vexing issue because normally we don't control all the source (e.g. third-party libraries that define constants). We're interested in detecting these dependencies from the perspective of using the constants. Since the reference is erased from the code that uses the constants, there is no easy way to detect them, short of doing source code analysis.

9条回答
等我变得足够好
2楼-- · 2019-01-05 02:44

To stop inlining you need to make the values non-compile time constants (the JLS term). You can do this without the use of functions and creating a minimum of bytecode by using a null in the initialiser expression.

public static final int INT_VALUE = null!=null?0: 1000;

Although it is very literal in its code generation, javac should optimise this to be a push of an immediate integer followed by a store to the static field in the static initialiser.

查看更多
看我几分像从前
3楼-- · 2019-01-05 02:45

JLS 13.4.9 deals with this issue. Their recommendation is to basically avoid compile-time constants if the value is in any way likely to change.

(One reason for requiring inlining of constants is that switch statements require constants on each case, and no two such constant values may be the same. The compiler checks for duplicate constant values in a switch statement at compile time; the class file format does not do symbolic linkage of case values.)

The best way to avoid problems with "inconstant constants" in widely-distributed code is to declare as compile time constants only values which truly are unlikely ever to change. Other than for true mathematical constants, we recommend that source code make very sparing use of class variables that are declared static and final. If the read-only nature of final is required, a better choice is to declare a private static variable and a suitable accessor method to get its value. Thus we recommend:

private static int N;
public static int getN() { return N; }

rather than:

public static final int N = ...;

There is no problem with:

public static int N = ...;

if N need not be read-only.

查看更多
forever°为你锁心
4楼-- · 2019-01-05 02:51

Rewrite class A like:

public class A {
    public static final int INT_VALUE;
    public static final String STRING_VALUE;

    static {
        INT_VALUE = 1000;
        STRING_VALUE = "foo";
    }
}
查看更多
登录 后发表回答