Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

jvm

javac

I've been running some microbenchmarks, and have come across an odd issue. I'm using java version "1.8.0_131" with the default compiler options.

Given a definition

public class JavaState {
    public String field = "hello";
    public final String finalField = "hello";
}

accessing field directly (state.field) generates

ALOAD 1
GETFIELD JavaState.field : Ljava/lang/String;

But accessing finalField directly (state.finalField) generates

ALOAD 1
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
LDC "hello"

Why bytecode calls Object->getClass() at a direct field access explains that the call to getClass is just to check that state is not null, but then the compiler has inlined the value of the field.

I might reasonably expect that substituting later versions of JavaState with different field values would result in other code seeing the change without recompilation, but this inlining prevents that. And my benchmarks show that if it is done in the name of performance it isn't working; at least on my benchmark Raspberry Pi, accessing finalField is 5-10% slower than accessing field.

What is the rationale for inlining the value of the final field?

like image 689
Duncan McGregor Avatar asked Oct 28 '17 23:10

Duncan McGregor


1 Answers

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.

like image 153
Jeffrey Bosboom Avatar answered Oct 17 '22 04:10

Jeffrey Bosboom