Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is it possible to disable javac's inlining of static final variables?

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.

like image 416
sjlee Avatar asked Aug 19 '10 16:08

sjlee


People also ask

Will static block be executed with final variable?

We can initialize a final static variable at the time of declaration. Initialize inside a static block : We can also initialize a final static variable inside a static block because we should initialize a final static variable before class and we know that static block is executed before main() method.

Can static methods use Final variables?

Final variable cannot be reinitialized. Static variables can be reinitialized. Final method can't be inherited. Static methods can only access the static members of the class and can only be called by other static methods.


6 Answers

Item 93 of Java Puzzlers (Joshua Bloch) says that you can work round this by preventing the final value from being considered a constant. For example:

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

Of course none of this is relevant if you don't have access to the code that defines the constants.

like image 132
DJClayworth Avatar answered Nov 13 '22 10:11

DJClayworth


I don't believe so. The simplest workaround would be to expose these as properties rather than fields:

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

Don't forget that in certain cases the inlining is essential to the use of the value - for example, if you were to use INT_VALUE as a case in a switch block, that has to be specified as a constant value.

like image 45
Jon Skeet Avatar answered Nov 13 '22 12:11

Jon Skeet


I think this is a serious bug. Java is not C/C++. There is a principle(or not) "Compile once, run everywhere".

In this case, when Class A is changed. Any Classes referencing A.CONST_VALUE must be re-compiled and they hardly know whether Class A is changed.

like image 39
neoedmund Avatar answered Nov 13 '22 12:11

neoedmund


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.

like image 28
Tom Hawtin - tackline Avatar answered Nov 13 '22 12:11

Tom Hawtin - tackline


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.

like image 31
Mark Peters Avatar answered Nov 13 '22 10:11

Mark Peters


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";
    }
}
like image 20
rveach Avatar answered Nov 13 '22 11:11

rveach