Hotspot JVM中AtomicInteger.lazySet()的解释器和JIT编译器的实现区别

The implementation difference between the interperter and the JIT compiler for AtomicInteger.lazySet() in the Hotspot JVM

关于AtomicInteger.lazySet()的背​​景,请参考AtomicInteger lazySet vs. set的现有讨论。

所以根据AtomicInteger.lazySet()的语义,在x86上CPU,AtomicInteger.lazySet()相当于对AotmicInteger的值进行一次正常的写操作,因为x86内存模型保证写操作之间的顺序。

但是,AtomicInteger.lazySet() 的运行时行为在 JDK 8 Hotspot JVM 中的解释器和 JIT 编译器(特别是 C2 编译器)之间是不同的,这让我很困惑。

首先,创建一个简单的 Java 应用程序进行演示。

import java.util.concurrent.atomic.AtomicInteger;

public class App {
    public static void main (String[] args) throws Exception {
        AtomicInteger i = new AtomicInteger(0);
        i.lazySet(1);
        System.out.println(i.get());
    }
}

然后转储C2编译器提供的instrinc方法中AtomicInteger.lazySet()的指令:

$ java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation -XX:CompileCommand=print,*AtomicInteger.lazySet App
...
      0x00007f1bd927214c: mov    %edx,0xc(%rsi)     ;*invokevirtual putOrderedInt
                                                ; - java.util.concurrent.atomic.AtomicInteger::lazySet@8 (line 110)

如您所见,操作符合预期,正常写入。

然后,使用 GDB 跟踪 AtomicInteger.lazySet() 的解释器的运行时行为。

$ gdb --args java App
(gdb) b Unsafe_SetOrderedInt

0x7ffff69ae836 callq  0x7ffff69b6642 <OrderAccess::release_store_fence(int volatile*, int)>     

0x7ffff69b6642: 
push   %rbp
mov    %rsp,%rbp
mov    %rdi,-0x8(%rbp)
mov    %esi,-0xc(%rbp)
mov    -0xc(%rbp),%eax
mov    -0x8(%rbp),%rdx
xchg   %eax,(%rdx)           // the write operation
mov    %eax,-0xc(%rbp)
nop
pop    %rbp
retq

s 你可以看到,该操作实际上是一个 XCHG 指令,它具有隐式锁语义,这带来了 AtomicInteger.lazySet() 旨在消除的性能开销。

有谁知道为什么会有这样的差异?谢谢。

在解释器中优化一个很少使用的操作没有多大意义。这会增加开发和维护成本,但没有明显的好处。

HotSpot 中的常见做法是仅在 JIT 编译器(C2 或 C1+C2)中实施优化。解释器实现只是工作,它不需要很快,因为如果代码对性能敏感,无论如何它都会被 JIT 编译。

因此,在解释器中(即在慢速路径上),Unsafe_SetOrderedInt 与易失性写入完全相同。