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

JavaDependenciesBytecodeJavac

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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.

Solution 3 - Java

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.

Solution 4 - Java

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.

Solution 5 - Java

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.

Solution 6 - Java

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

Solution 7 - Java

jmake is an open-source project that claims to do the whole job of keeping track of dependencies between Java files and incrementally compiling the minimum set of files required. It claims to correctly handle changes to static final constants, albeit by sometimes requiring the whole project to be recompiled. It even handles changes at a finer granularity than classfiles; if (for example) the signature of a method C.m() changes, then it only recompiles the classes that actually depend on m() rather than all classes that use C.

DISCLAIMER: I have no experience using jmake.

Solution 8 - Java

I recently came across a similar issue, and, as it was said above, such inlining can be worked around using non-compile-time expressions, say:

public final class A {

	public static final int INT_VALUE = constOf(1000);
	public static final String STRING_VALUE = constOf("foo");

}

where the constOf method family is merely:

// @formatter:off
public static boolean constOf(final boolean value) { return value; }
public static byte constOf(final byte value) { return value; }
public static short constOf(final short value) { return value; }
public static int constOf(final int value) { return value; }
public static long constOf(final long value) { return value; }
public static float constOf(final float value) { return value; }
public static double constOf(final double value) { return value; }
public static char constOf(final char value) { return value; }
public static <T> T constOf(final T value) { return value; }
// @formatter:on

This is a bit shorter than other suggestions like Integer.valueOf(1000).intValue() or null!=null?0: 1000

Solution 9 - Java

I feel java tightly relies on dynamic compilation and it doesn't do any fancy compilation logic as like C++.

you can try out some options with JIT compilers which does run-time optimization which may have some options to disable/enable this.

in default javac you may not get that option. you have to use

  1. some type of dependency graph like extends or implements
  2. using method based linking.

-s

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsjleeView Question on Stackoverflow
Solution 1 - JavaDJClayworthView Answer on Stackoverflow
Solution 2 - JavaJon SkeetView Answer on Stackoverflow
Solution 3 - JavaTom Hawtin - tacklineView Answer on Stackoverflow
Solution 4 - JavaMark PetersView Answer on Stackoverflow
Solution 5 - JavaneoedmundView Answer on Stackoverflow
Solution 6 - JavarveachView Answer on Stackoverflow
Solution 7 - JavamhaggerView Answer on Stackoverflow
Solution 8 - JavaLyubomyr ShaydarivView Answer on Stackoverflow
Solution 9 - JavakadalamittaiView Answer on Stackoverflow