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

9

62

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.

Omniscient answered 19/8, 2010 at 16:53 Comment(1)
Good Question!!! Sometime this inlining of constants cause weird issues... But I haven't seen a proper way to restrict it.Saprophyte
L
47

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.

Landtag answered 19/8, 2010 at 17:11 Comment(6)
+1; Bloch did cover this in Java Puzzlers. He uses ident instead of toX.Salisbury
@DJClayworth: Nice. Didn't think of that :) For most types there are probably existing methods which will work (e.g. String.valueOf())Outlier
I just remembered the assignment of System.out is this: public final static PrintStream out = nullPrintStream(); (the actual stream is set later by native code). nullPrintStream() returns null if currentTimeMillis()>0. It never hit me why they did that (as opposed to just ...out = null;) until now. Of course if I had bothered to read the documentation for nullInputStream() it would have been made clear...Virtual
The inability to disable this optimization at compile time is one of many ways javac cripples the ability to create a proper debugger.Phytobiology
@MarkPeters that’s one of those esoteric optimizations, I love the Java core developers for. Since neither, objects of type PrintStream nor the null reference can ever be compile-time constants, this inlining can only refer to the JIT, but the JIT doesn’t care where the value came from before the assignment. Note that starting with Java 7, the code does look like public final static PrintStream out = null;.Hereunder
Regarding fields which could truly be compile-time constants, like in this answer, it would be more efficient to use public static final int INT_VALUE; public static final String STRING_VALUE; static { INT_VALUE = 1000; STRING_VALUE = "foo"; } which avoids unnecessary method invocations, not to speak of the boxing overhead of Integer.valueOf(1000).Hereunder
O
14

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.

Outlier answered 19/8, 2010 at 17:2 Comment(5)
Jon, do you have any insight as to why the JLS suggests the accessor, but does not suggest privately using the final modifier (see my answer)?Virtual
@Mark - Wouldn't it be possible for there to be inlining of the accessor function if the private member was final (the result could be determined at compile time), essentially recreating the public static final scenario? (speculative reasoning on my part)Dialyse
@Tim: My intuition would be that functions may only be inlined at runtime. Otherwise any stub method you create (return null;) would require you to recompile all classes utilizing it after you got around to implementing that method.Virtual
@Mark - Ah, of course. Bit of a mental fail there, sorry about that. :)Dialyse
@Mark - one reason is the fact that no code sets the (private) field makes a final model redundant ... from a purely linguistic sense. (Style considerations say otherwise, but the JLS is not trying to be a style manual.)Upton
J
9

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.

Joijoice answered 19/8, 2010 at 18:4 Comment(5)
How about just 1000+0. But would that then work in a case statement?Landonlandor
@Landonlandor 1000+0 is a compile-time constant. It's no different from 1000 so it will work in a case statement, but then it will also inline. The magic in the answer is that null is not a compile-time constant and therefore no expression that uses it is either.Joijoice
If you want to be sure that the compiler just generates an assignment, you can simply write it as such, public static final int INT_VALUE; static { INT_VALUE = 1000; }Hereunder
@Hereunder Whilst that's clearer way of producing exactly the same bytecode, I think there's more disadvantages. null!=null?0: clearly indicates an intent to do something so is less likely to be unplugged by a cleaner. Although the idiom isn't obvious, it shouldn't take long to work out what the result is, even if not the compile-time constant semantics*. It's not going to get totally mangled by reformatting. You don't have to match identifiers (do they match? don't they? Is it a weird spelling thing?).Joijoice
I didn’t insist on having it in one line; that’s just how comments are formatted here. So if a formatter breaks it into multiple lines, I’m fine with that. A cleaner, smart enough to understand that null!=null is meaningful, should have no problems with understanding the difference between compile-time constants and variables assigned in an initializer. Since a static final variable not immediately assigned must get assigned in a static initializer and it must get assigned exactly once, the compiler will check it. It’s the standard idiom for initializers not fitting into one expression.Hereunder
V
8

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.

Virtual answered 19/8, 2010 at 16:53 Comment(0)
L
4

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.

Lotz answered 7/9, 2017 at 6:41 Comment(9)
It's not a bug, it's a feature. It makes sure that if you have a constant in an object in another module that might not even be loaded, so when you ask for a BANANA, it does not go getting the Monkey class and the jungle package along with it. Java offers reflection and serialization(along with others) to avoid caching, but no mechanism to explicitly force inlining, so i'm euphoric it actually does inline it(if you delete the utility class with constants, the class using those constants will still work). Other solutions propose ways to do it just by using non compile time constants.Septuplet
It is a bug. Remember that Java does JIT compiling, so it is a non-optimization that occasionally causes major grief.Landonlandor
@Landonlandor it looks easy, when you only consider reading a static field instead of using a value. But imagine how a switch statement over constants of another class should get compiled, when it ought to support changes after compilation. Likewise, the language is strict when it comes to code reachability. Allowing constants to change after compilation would defeat the language safety. And it is consistent to force all uses of a constant to use the constant rather than to read a dynamic value.Hereunder
@Hereunder The javac is not actually the java compiler. It is just the java parser. It is the JIT that optimizes. So interpreted CASE is just a hash table. Likewise, Java should have keyword optional parameters, just like every other pre-C language did, and efficiency is not an issue like it is with C++, say. Maybe we will see the latter given C# has had it for some time.Landonlandor
@Landonlandor javac produces bytecode. That bytecode has to contain a compiled form of the switch table, regardless of how it looks like. Now tell, how the compiled form should look like, if the key values were allowed to change after compilation. You say “hash table”, but hash codes also depend on the actual value, hence, do not allow changing key values. Don’t try to distract from the topic with your “optional parameters” stuff. Does either, C++ or C#, allow the key values of a switch statement to change after compilation? I don’t think so.Hereunder
@Holger, it would be just like an array initialized with expressions. Or a bunch of calls to a hash add function. Or some special byte code that the loader can resolve. And we now have Strings in hash tables which need a bit of work anyway. Lots of ways. But it don't, and every so often that trips people up badly.Landonlandor
@Landonlandor the strings in switch statements still require constant labels. That’s how switch works. If you want “an array initialized with expressions” instead or some hashing structure that can cope with changing keys, well, just implement that. “Lots of ways” But don’t blame Java for not doing something entirely different than written into the source code.Hereunder
@Holger, the common jump table optimization technique for switch statements needs constants by run time. But that is well after javac time. It could be load time. Or JIT time. Further, that optimization is neither necessary nor sufficient. A good compiler should use a jump table for "if (x==1) ... elseif (x==2)... elseif (x==3)...". Likewise a switch without constants that cannot be easily optimized is also very useful and supported in other languages. Java is much more sophisticated than C!Landonlandor
@Landonlandor ok, I should have said “That’s how Java’s switch works”. It works that way and it requires constant labels. Yes, other languages support other constructs. But that never was the topic. The topic is constant fields in Java. The way, switch works in Java, the way, annotations work in Java, and the way, code reachability checks work in Java, mandate non-changing, resp. copied constant values, even if only implicit. Changing these, is not as trivial as with a simple field read. That’s the point. The mandatory code flow checks are way more interesting than the switch statement.Hereunder
S
1

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";
    }
}
Seibel answered 3/6, 2018 at 1:53 Comment(0)
F
0

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.

Frear answered 18/1, 2011 at 5:29 Comment(1)
Dead link, and seems to be a dead project.Landonlandor
P
0

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

Plumage answered 13/1, 2017 at 11:1 Comment(0)
W
-5

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

Wohlert answered 19/8, 2010 at 17:16 Comment(1)
I am not sure what you mean... This is strictly the javac (static compiler) behavior, and it seems one that's really dictated by the language spec at that.Omniscient

© 2022 - 2024 — McMap. All rights reserved.