How to Disable Javac's Inlining of Static Final Variables

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

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.

javac treating static final differently based on assignment method

In the first case, the compiler does an optimization. It knows Foo will always be false and kill the code than will never be reached.

In the second case, you are assigning the value of the non-final variable isBar to BAR. The compiler can't tell if the variable isBar has been modified somewhere else, especially if it is not private. Therefore it is not sure of the value of BAR. Therefore he can not do the optimization.

Why does the Java compiler inline access to non-static final fields?

This may be mandated by the Java Language Specification, but the details are unclear. From Section 4.12.4 final Variables:

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).

Note there is no requirement that the variable be static. Then from Section 13.1 The Form of a Binary:

A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.

If such a field is static, [...]

If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).

I'm not sure where your decompiled code comes from. If it's outside the class, then what you see is mandated by the Specification. If it's inside the class, it's less clear. You could read the third paragraph in the above quote to imply that the only code reference to the field should be in <init> methods initializing the field, but this is not actually stated.

Section 13.4.9 directly addresses your concern about binary compatibility, but seems to limit itself explicitly to static fields (emphasis mine):

If a field is a constant variable (§4.12.4), and moreover is static, then deleting the keyword final or changing its value will not break compatibility with pre-existing binaries by causing them not to run, but they will not see any new value for a usage of the field unless they are recompiled. This result is a side-effect of the decision to support conditional compilation (§14.21). (One might suppose that the new value is not seen if the usage occurs in a constant expression (§15.28) but is seen otherwise. This is not so; pre-existing binaries do not see the new value at all.)

Another reason for requiring inlining of values of static constant variables is because of switch statements. They are the only kind of statement that relies on constant expressions, namely that each case label of a switch statement must be a constant expression whose value is different than every other case label. case labels are often references to static constant variables so it may not be immediately obvious that all the labels have different values. If it is proven that there are no duplicate labels at compile time, then inlining the values into the class file ensures there are no duplicate labels at run time either - a very desirable property.

As non-static constant final fields are not commonly used nor especially useful, it's possible they simply slipped through the cracks when writing the Specification.

Class Loading vs Initialisation: Java static final variable

According to the Java language specification section 12.4.1 (emphasis added):

A class or interface type T will be initialized immediately before
the first occurrence of any one of the following:

  • T is a class and an instance of T is created.

  • A static method declared by T is invoked.

  • A static field declared by T is assigned.

  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

A constant variable is a final variable that is initialized with a constant expression. In your code, Example.i is a constant variable, and therefore does not cause the class to be loaded.

So if the class is not loaded, where does the value com from?

The language specification requires the compiler to inline its value. From section of binary compatibility 13.1:


  1. A reference to a field that is a constant variable (§4.12.4) must be
    resolved at compile time to the value V denoted by the constant
    variable's initializer.

    If such a field is static, then no reference to the field should be
    present in the code in a binary file, including the class or interface
    which declared the field. Such a field must always appear to have been
    initialized (§12.4.2); the default initial value for the field (if
    different than V) must never be observed.

Does the JLS require inlining of final String constants?

Yes, the “inlining” behavior is mandated by the specification:

13.1. The Form of a Binary



  1. A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.

    If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than V) must never be observed.

    If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).

Note, how this precisely addresses you scenario: “If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field”.

In other words, if you encounter a compiler not adhering to this, you found a compiler bug.


As an addendum, the starting point for finding this information was:

4.12.4. final Variables


A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).

Are all compile-time constants inlined?

You can use String.intern() to get the desired effect, but should comment your code, because not many people know about this. i.e.

public static final String configOption1 = "some option".intern();

This will prevent the compile time inline. Since it is referring to the exact same string that the compiler will place in the perm, you aren't creating anything extra.

As an alternative you could always do

public static final String configOption1 = "some option".toString();

which might be easier to read. Either way, since this is a bit odd you should comment the code to inform those maintaining it what you are doing.

Edit:
Found another SO link that gives references to the JLS, for more information on this.
When to use intern() on String literals

Android version checking and inlining of static final constants

What is inlined is constants that can be determined at compile time, such as:

private final int CONST = 1;

If you check the source code (it's an old version but I suppose it has not changed much), the constants look like this:

public static final String SDK = getString("ro.build.version.sdk");

And this is the getString method:

private static String getString(String property) {
return SystemProperties.get(property, UNKNOWN);
}

So the constant can't be determined at compile time.

Compiler optimization of public static final and OSGi

The value of a referenced static final primitive or string is directly inlined into the using class. So unrelated to OSGI and any visibility rules, b2 will still contain an embedded MYVAL value of "a".

I think this is documented in the Java Language Specification, Section 13.4:

If a field is a constant variable (§4.12.4), then deleting the keyword final or changing its value will not break compatibility with pre-existing binaries by causing them not to run, but they will not see any new value for the usage of the field unless they are recompiled. This is true even if the usage itself is not a compile-time constant expression (§15.28).

This answer to a similar question shows a workaround. Changing the declaration so it is no longer a constant expression disables this inlining behaviour.

public static final String MYVAL = String.valueOf("a");

Avoiding Replacing Constant Reference With a Literal When Compiled

You just need something that isn't a compile-time constant expression. For example, method invocations aren't. So just adding .intern() at the end of each literal will make it escape the rules. Then each referring site will have to ensure the target type is initialized and read the current value.

A compiler option that changes this behavior is impossible because it would violate the Java Language Specification.



Related Topics



Leave a reply



Submit