静态最终字段与 TrustFinalNonStaticFields
static final fields vs TrustFinalNonStaticFields
假设我有这个简单的方法:
static final Integer me = Integer.parseInt("2");
static int go() {
return me * 2;
}
对于 javac,me
不是常量(根据 JLS 规则),但对于 JIT 很可能是。
我尝试用以下方法进行测试:
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;
}
}
并且 运行将其与:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,StaticFinal.go"
-XX:PrintAssemblyOptions=intel
StaticFinal.java
我不知道汇编很好,但这很明显:
mov eax,0x4
go
的结果立即是 4
,即:JIT“信任”me
是一个常量,因此 2 * 2 = 4
。
如果我删除 static
并将代码更改为:
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;
}
}
和 运行 与:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
我确实在汇编中看到:
shl eax,1
实际上是 me
与 2
的乘积,通过移位完成。所以 JIT 不相信 me
是一个常量,这是意料之中的。
现在是问题。我认为如果我添加 TrustFinalNonStaticFields
标志,我会看到相同的 mov eax 0x4
,即:运行:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:+UnlockExperimentalVMOptions
-XX:+TrustFinalNonStaticFields
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
应该显示 mov eax,0x4
,但令我惊讶的是它没有,代码保持为:
shl eax,1
有人可以解释发生了什么以及我缺少什么吗?
TrustFinalNonStaticFields
允许折叠来自常量对象的 final
实例字段。但是,在您的示例中, instance
字段不是常量,因此折叠 me
字段的负载是不正确的,因为 instance
对象在编译后的某个时刻可能仍会更改。
此外,您正在打印 go
方法的程序集,其中 this
如果该方法是单独编译的,则不会被视为常量。要查看 TrustFinalNonStaticFields
的效果,您需要查看 go
方法的内联版本的程序集,其中接收者是常量。例如:
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;
}
}
运行:
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
生成程序集,我们可以看到 me
字段的加载 + 乘法正在 payload
方法中折叠:
# {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
与禁用 TFNSF 的版本相比,me
字段的加载仍然发生:
# {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
假设我有这个简单的方法:
static final Integer me = Integer.parseInt("2");
static int go() {
return me * 2;
}
对于 javac,me
不是常量(根据 JLS 规则),但对于 JIT 很可能是。
我尝试用以下方法进行测试:
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;
}
}
并且 运行将其与:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,StaticFinal.go"
-XX:PrintAssemblyOptions=intel
StaticFinal.java
我不知道汇编很好,但这很明显:
mov eax,0x4
go
的结果立即是 4
,即:JIT“信任”me
是一个常量,因此 2 * 2 = 4
。
如果我删除 static
并将代码更改为:
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;
}
}
和 运行 与:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
我确实在汇编中看到:
shl eax,1
实际上是 me
与 2
的乘积,通过移位完成。所以 JIT 不相信 me
是一个常量,这是意料之中的。
现在是问题。我认为如果我添加 TrustFinalNonStaticFields
标志,我会看到相同的 mov eax 0x4
,即:运行:
java -XX:+UnlockDiagnosticVMOptions
-XX:-TieredCompilation
"-XX:CompileCommand=print,NonStaticFinal.go"
-XX:+UnlockExperimentalVMOptions
-XX:+TrustFinalNonStaticFields
-XX:PrintAssemblyOptions=intel
NonStaticFinal.java
应该显示 mov eax,0x4
,但令我惊讶的是它没有,代码保持为:
shl eax,1
有人可以解释发生了什么以及我缺少什么吗?
TrustFinalNonStaticFields
允许折叠来自常量对象的 final
实例字段。但是,在您的示例中, instance
字段不是常量,因此折叠 me
字段的负载是不正确的,因为 instance
对象在编译后的某个时刻可能仍会更改。
此外,您正在打印 go
方法的程序集,其中 this
如果该方法是单独编译的,则不会被视为常量。要查看 TrustFinalNonStaticFields
的效果,您需要查看 go
方法的内联版本的程序集,其中接收者是常量。例如:
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;
}
}
运行:
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
生成程序集,我们可以看到 me
字段的加载 + 乘法正在 payload
方法中折叠:
# {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
与禁用 TFNSF 的版本相比,me
字段的加载仍然发生:
# {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