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?
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