这发生了令人惊讶的转变。
首先可以说(相对肯定)的是,该效果是由 JIT 引起的。我将代码片段合并到这个 MCVE 中:
public class CounterJitTest
{
private static class Counter
{
private int count;
public void increase()
{
count++;
}
public void decrease()
{
count--;
}
public int getCount()
{
return count;
}
}
private static class Person extends Thread
{
private Counter c;
public Person(Counter c)
{
this.c = c;
}
@Override
public void run()
{
for (int i = 0; i < 1000000; i++)
{
c.increase();
c.decrease();
}
}
}
public static void main(String[] args) throws InterruptedException
{
for (int i = 0; i < 10; i++)
{
Counter c = new Counter();
Person p1 = new Person(c);
Person p2 = new Person(c);
p1.start();
p2.start();
p1.join();
p2.join();
System.out.println("run " + i + ": " + c.getCount());
}
}
}
运行它
java CounterJitTest
导致问题中提到的输出:
run 0: 6703
run 1: 178
run 2: 1716
run 3: 0
run 4: 0
run 5: 0
run 6: 0
run 7: 0
run 8: 0
run 9: 0
关闭 JIT-Xint
(解释模式),即启动为
java -Xint CounterJitTest
导致以下结果:
run 0: 38735
run 1: 53174
run 2: 86770
run 3: 27244
run 4: 61885
run 5: 1746
run 6: 32458
run 7: 52864
run 8: 75978
run 9: 22824
为了更深入地了解 JIT 的实际含义does,我在 HotSpot 反汇编器虚拟机中启动了整个过程,以查看生成的程序集。然而,执行时间太快了,我想:好吧,我只需增加计数器for
-loop:
for (int i = 0; i < 1000000; i++)
但即使增加到100000000
导致程序立即完成。这已经引起了怀疑。生成反汇编后
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly -XX:+PrintInlining CounterJitTest
我查看了编译版本increase
and decrease
方法,但没有发现任何明显的东西。但是,那run
方法似乎是这里的罪魁祸首。最初,组装run
方法包含预期的代码(此处仅发布最相关的部分):
Decoding compiled method 0x0000000002b32fd0:
Code:
[Entry Point]
[Constants]
# {method} {0x00000000246d0f00} 'run' '()V' in 'CounterJitTest$Person'
...
[Verified Entry Point]
...
0x0000000002b33198: je 0x0000000002b33338 ;*iconst_0
; - CounterJitTest$Person::run@0 (line 35)
0x0000000002b3319e: mov $0x0,%esi
0x0000000002b331a3: jmpq 0x0000000002b332bc ;*iload_1
; - CounterJitTest$Person::run@2 (line 35)
0x0000000002b331a8: mov 0x178(%rdx),%edi ; implicit exception: dispatches to 0x0000000002b3334f
0x0000000002b331ae: shl $0x3,%rdi ;*getfield c
; - CounterJitTest$Person::run@9 (line 37)
0x0000000002b331b2: cmp (%rdi),%rax ;*invokevirtual increase
; - CounterJitTest$Person::run@12 (line 37)
; implicit exception: dispatches to 0x0000000002b33354
...
0x0000000002b33207: je 0x0000000002b33359
0x0000000002b3320d: mov 0xc(%rdi),%ebx ;*getfield count
; - CounterJitTest$Counter::increase@2 (line 9)
; - CounterJitTest$Person::run@12 (line 37)
0x0000000002b33210: inc %ebx
0x0000000002b33212: mov %ebx,0xc(%rdi) ;*putfield count
; - CounterJitTest$Counter::increase@7 (line 9)
; - CounterJitTest$Person::run@12 (line 37)
...
0x0000000002b3326f: mov %ebx,0xc(%rdi) ;*putfield count
; - CounterJitTest$Counter::decrease@7 (line 14)
; - CounterJitTest$Person::run@19 (line 38)
...
诚然,我并不深刻地“理解”这一点,但人们可以看到它确实起到了作用getfield c
,以及(部分内联?)的一些调用increase
and decrease
方法。
但是,那final的编译版本run
方法是这样的:
Decoding compiled method 0x0000000002b34590:
Code:
[Entry Point]
[Constants]
# {method} {0x00000000246d0f00} 'run' '()V' in 'CounterJitTest$Person'
# [sp+0x20] (sp of caller)
0x0000000002b346c0: mov 0x8(%rdx),%r10d
0x0000000002b346c4:
<writer thread='2060'/>
[Loaded java.lang.Shutdown from C:\Program Files\Java\jre1.8.0_131\lib\rt.jar]
<writer thread='5944'/>
shl $0x3,%r10
0x0000000002b346c8: cmp %r10,%rax
0x0000000002b346cb: jne 0x0000000002a65f60 ; {runtime_call}
0x0000000002b346d1: data32 xchg %ax,%ax
0x0000000002b346d4: nopw 0x0(%rax,%rax,1)
0x0000000002b346da: nopw 0x0(%rax,%rax,1)
[Verified Entry Point]
0x0000000002b346e0: mov %eax,-0x6000(%rsp)
0x0000000002b346e7: push %rbp
0x0000000002b346e8: sub $0x10,%rsp ;*synchronization entry
; - CounterJitTest$Person::run@-1 (line 35)
0x0000000002b346ec: cmp 0x178(%rdx),%r12d
0x0000000002b346f3: je 0x0000000002b34701
0x0000000002b346f5: add $0x10,%rsp
0x0000000002b346f9: pop %rbp
0x0000000002b346fa: test %eax,-0x1a24700(%rip) # 0x0000000001110000
; {poll_return}
0x0000000002b34700: retq
0x0000000002b34701: mov %rdx,%rbp
0x0000000002b34704: mov $0xffffff86,%edx
0x0000000002b34709: xchg %ax,%ax
0x0000000002b3470b: callq 0x0000000002a657a0 ; OopMap{rbp=Oop off=80}
;*aload_0
; - CounterJitTest$Person::run@8 (line 37)
; {runtime_call}
0x0000000002b34710: int3 ;*aload_0
; - CounterJitTest$Person::run@8 (line 37)
0x0000000002b34711: hlt
0x0000000002b34712: hlt
0x0000000002b34713: hlt
0x0000000002b34714: hlt
0x0000000002b34715: hlt
0x0000000002b34716: hlt
0x0000000002b34717: hlt
0x0000000002b34718: hlt
0x0000000002b34719: hlt
0x0000000002b3471a: hlt
0x0000000002b3471b: hlt
0x0000000002b3471c: hlt
0x0000000002b3471d: hlt
0x0000000002b3471e: hlt
0x0000000002b3471f: hlt
[Exception Handler]
[Stub Code]
0x0000000002b34720: jmpq 0x0000000002a8c9e0 ; {no_reloc}
[Deopt Handler Code]
0x0000000002b34725: callq 0x0000000002b3472a
0x0000000002b3472a: subq $0x5,(%rsp)
0x0000000002b3472f: jmpq 0x0000000002a67200 ; {runtime_call}
0x0000000002b34734: hlt
0x0000000002b34735: hlt
0x0000000002b34736: hlt
0x0000000002b34737: hlt
这是complete组装方法!它确实……嗯,基本上什么也没做。
为了证实我的怀疑,我明确地disabled的内联increase
方法,从开始
java -XX:CompileCommand=dontinline,CounterJitTest$Counter.increase CounterJitTest
输出再次符合预期:
run 0: 3497
run 1: -71826
run 2: -22080
run 3: -20893
run 4: -17
run 5: -87781
run 6: -11
run 7: -380
run 8: -43354
run 9: -29719
所以我的结论是:
JIT 内联了increase
and decrease
方法。它们仅递增和递减相同的值。内联之后,JIT 足够聪明,可以找出调用的顺序
c.increase();
c.decrease();
本质上是一个no-op,因此,就是这样做:什么也没有。