0

Refer to the existing discussion at AtomicInteger lazySet vs. set for the background of AtomicInteger.lazySet().

So according to the semantics of AtomicInteger.lazySet(), on x86 CPU, AtomicInteger.lazySet() is equivalent to a normal write operation to the value of the AotmicInteger, because the x86 memory model guarantees the order among write operations.

However, the runtime behavior for AtomicInteger.lazySet() is different between the interperter and the JIT compiler (the C2 compiler specifically) in the JDK 8 Hotspot JVM, which confuses me.

First, create a simple Java application for demo.

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());
    }
}

Then, dump the instructions for AtomicInteger.lazySet() which are from the instrinc method provided by the C2 compiler:

$ 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)

As you can see, the operation is as expected a normal write.

Then, use the GDB to trace the runtime behavior of the interpreter for 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 you can see, the operation is actually a XCHG instruction ,which has a implict lock semantics, which brings performance overhead that AtomicInteger.lazySet() is intended to eliminate.

Does anyone know why there is such a difference? thanks.

1 Answers1

1

There is no much sense in optimizing a rarely used operation in the interpreter. This would increase development and maintenance costs for no visible benefit.

It's a common practice in HotSpot to implement optimizations only in a JIT compiler (either C2 or both C1+C2). The interpreter implementation just works, it does not need to be fast, because if the code is performance sensitive, it will be JIT-compiled anyway.

So, in the interpreter (i.e. on the slow path), Unsafe_SetOrderedInt is exactly the same as a volatile write.

apangin
  • 92,924
  • 10
  • 193
  • 247
  • thanks. It looks you are right. Because I find a comment in the jdk8 hotspot source code (https://github.com/openjdk/jdk8u/blob/master/hotspot/src/share/vm/prims/unsafe.cpp#L448) , that is "The non-intrinsified versions of setOrdered just use setVolatile". – hiphoon sun Jul 20 '21 at 13:56