Suppose I have this simple method:
static final Integer me = Integer.parseInt("2");
static int go() {
return me * 2;
}
For javac, me
is not a constant (according to the JLS rules), but for JIT most probably is.
I tried to test this with:
public class StaticFinal {
public static void main(String[] args) {
int hash = 0;
for(int i=0;i<1000_000;++i){
hash = hash ^ go();
}
System.out.println(hash);
}
static final Integer me = Integer.parseInt("2");
static int go() {
return me * 2;
}
}
And running it with:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,StaticFinal.go"
-XX:PrintAssemblyOptions=intel
StaticFinal.java
I do not know assembly very good, but this is obvious:
mov eax,0x4
The result of go
is immediately 4
, i.e.: JIT "trusted" me
to be a constant, thus 2 * 2 = 4
.
If I drop static
and change the code to:
public class NonStaticFinal {
static NonStaticFinal instance = new NonStaticFinal();
public static void main(String[] args) {
int hash = 0;
for(int i=0;i<1000_000;++i){
hash = hash ^ instance.go();
}
System.out.println(hash);
}
final Integer me = Integer.parseInt("2");
int go() {
return me * 2;
}
}
And run that with:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
I do see in assembly:
shl eax,1
which is actually the multiplication of me
with 2
, done via a shift. So JIT did not trust me
to be a constant, which is kind of expected.
And now the question. I thought that if I add TrustFinalNonStaticFields
flag, I will see the same mov eax 0x4
, i.e.: running with:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:+UnlockExperimentalVMOptions
-XX:+TrustFinalNonStaticFields
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
should reveal mov eax,0x4
, but to my surprise it does not, and the code stays as:
shl eax,1
Can someone explain what is going on and what I am missing?
No, absolutely not - and it's not a convention. static and final are entirely different things. static means that the field relates to the type rather than any particular instance of the type. final means that the field can't change value after initial assignment (which must occur during type/instance initialization).
A final field cannot have its value changed. A final field must have an initial value assigned to it, and once set, the value cannot be changed again. A final field is often also declared static . A field declared static and final is also called a "constant".
The main difference between static and final is that the static is used to define the class member that can be used independently of any object of the class. In contrast, final is used to declare a constant variable or a method that cannot be overridden or a class that cannot be inherited.
A developer needs to combine the keywords static final to achieve this in Java. The static keyword means the value is the same for every instance of the class. The final keyword means once the variable is assigned a value it can never be changed.
TrustFinalNonStaticFields
enables folding of final
instance fields from constant objects. In your example however, the instance
field is non constant, so folding the load of the me
field is not correct, since the instance
object might still be changed at some point after compilation.
Furthermore, you're printing out the assembly for the go
method, where this
will not be seen as a constant if the method is compiled in isolation. To see the effect of TrustFinalNonStaticFields
you need to look at the assembly for an inlined version of the go
method, where the receiver is a constant. For instance:
public class NonStaticFinal {
static final NonStaticFinal instance = new NonStaticFinal();
public static void main(String[] args) {
for (int i = 0; i < 20_000; i++) { // trigger compilation of 'payload'
payload();
}
}
static int payload() {
return instance.go();
}
final Integer me = Integer.parseInt("2");
int go() {
return me * 2;
}
}
Running with:
java
-XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.payload"
"-XX:CompileCommand=dontinline,NonStaticFinal.payload"
-XX:+UnlockExperimentalVMOptions
-XX:+TrustFinalNonStaticFields
-XX:PrintAssemblyOptions=intel
-Xbatch
NonStaticFinal.java
Produces assembly where we can see the load + multiplication of the me
field is being folded in the payload
method:
# {method} {0x0000016238c59470} 'payload' '()I' in 'NonStaticFinal'
# [sp+0x20] (sp of caller)
// set up frame
0x00000162283d2500: sub rsp,18h
0x00000162283d2507: mov qword ptr [rsp+10h],rbp ;*synchronization entry
; - NonStaticFinal::payload@-1 (line 12)
// load a constant 4
0x00000162283d250c: mov eax,4h <-------------
// clean up frame
0x00000162283d2511: add rsp,10h
0x00000162283d2515: pop rbp
// safepoint poll
0x00000162283d2516: mov r10,qword ptr [r15+110h]
0x00000162283d251d: test dword ptr [r10],eax ; {poll_return}
// return
0x00000162283d2520: ret
Compared to the version where TFNSF is disabled, where the load of the me
field still occurs:
# {method} {0x00000245f9669470} 'payload' '()I' in 'NonStaticFinal'
# [sp+0x20] (sp of caller)
// stack bang
0x00000245e8d52a00: mov dword ptr [rsp+0ffffffffffff9000h],eax
// set up frame
0x00000245e8d52a07: push rbp
0x00000245e8d52a08: sub rsp,10h ;*synchronization entry
; - NonStaticFinal::payload@-1 (line 12)
// load the 'instance' field. It's a constant, so the address here is constant
0x00000245e8d52a0c: mov r10,70ff107a8h ; {oop(a 'NonStaticFinal'{0x000000070ff107a8})}
// load the (compressed) oop 'me' field at 0ch (first field after the object header)
0x00000245e8d52a16: mov r11d,dword ptr [r10+0ch] ;*getfield me {reexecute=0 rethrow=0 return_oop=0}
; - NonStaticFinal::go@1 (line 18)
; - NonStaticFinal::payload@3 (line 12)
// Load the 'value' field from the Integer object.
// r12 is the heap base, r11 the compressed oop 'Integer', *8 here to uncompress it,
// and again loading the first field after the header at 0ch
0x00000245e8d52a1a: mov eax,dword ptr [r12+r11*8+0ch]; implicit exception: dispatches to 0x00000245e8d52a31
// multiply by 2
// ABI returns ints in the 'eax' register, so no need to shuffle afterwards
0x00000245e8d52a1f: shl eax,1h ;*imul {reexecute=0 rethrow=0 return_oop=0}
; - NonStaticFinal::go@8 (line 18)
; - NonStaticFinal::payload@3 (line 12)
// clean up frame
0x00000245e8d52a21: add rsp,10h
0x00000245e8d52a25: pop rbp
// safepoint poll
0x00000245e8d52a26: mov r10,qword ptr [r15+110h]
0x00000245e8d52a2d: test dword ptr [r10],eax ; {poll_return}
// return
0x00000245e8d52a30: ret
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With