Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value integrity guarantee for concurrent long writes in 64-bit OpenJDK 7/8

Note: this question isn't related to volatile, AtomicLong, or any perceived deficiency in the described use case.

The property I am trying to prove or rule out is as follows:

Given the following:

  • a recent 64-bit OpenJDK 7/8 (preferably 7, but 8 also helpful)
  • a multiprocessing Intel-base system
  • a non-volatile long primitive variable
  • multiple unsynchronized mutator threads
  • an unsynchronized observer thread

Is the observer always guaranteed to encounter intact values as written by a mutator thread, or is word tearing a danger?

JLS: Inconclusive

This property exists for 32-bit primitives and 64-bit object references, but isn't guaranteed by the JLS for longs and doubles:

17.7. Non-atomic Treatment of double and long:
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

But hold your horses:

[...] For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts. Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. [...]

So, the JLS allows JVM implementations to split 64-bit writes, and encourages developers to adjust accordingly, but also encourages JVM implementors to stick with 64-bit writes. We don't have an answer for recent versions of HotSpot yet.

HotSpot JIT: Careful Optimism

Since word tearing is most likely to occur within the confines of tight loops and other hotspots, I've tried to analyze actual assembly output from JIT compilation. To make a long story short: further testing is needed, but I can only see atomic 64-bit operations on longs.

I used hdis, a disassembler plugin for OpenJDK. Having built and installed the plugin in my aging OpenJDK 7u25 build, I proceeded to write a short program:

public class Counter {
  static long counter = 0;
  public static void main(String[] _) {
    for (long i = (long)1e12; i < (long)1e12 + 1e5; i++)
      put(i);
    System.out.println(counter);
  }

  static void put(long v) {
    counter += v;
  }
}

I made sure to always use values larger than MAX_INT (1e12 to 1e12+1e5), and repeat the operation enough times (1e5) to trigger JIT.

After compilation, I executed Counter.main() with hdis, like so:

java -XX:+UnlockDiagnosticVMOptions \ 
     -XX:PrintAssemblyOptions=intel \
     -XX:CompileCommand=print,Counter.put \ 
     Counter

The assembly generated for Counter.put() by the JIT was as follows (decimal line numbers added for convenience):

01   # {method} 'put' '(J)V' in 'Counter'
02 ⇒ # parm0:    rsi:rsi   = long
03   #           [sp+0x20]  (sp of caller)
04   0x00007fdf61061800: sub    rsp,0x18
05   0x00007fdf61061807: mov    QWORD PTR [rsp+0x10],rbp  ;*synchronization entry
06                                                 ; - Counter::put@-1 (line 15)
07   0x00007fdf6106180c: movabs r10,0x7d6655660    ;   {oop(a 'java/lang/Class' = 'Counter')}
08 ⇒ 0x00007fdf61061816: add    QWORD PTR [r10+0x70],rsi  ;*putstatic counter
09                                                 ; - Counter::put@5 (line 15)
10   0x00007fdf6106181a: add    rsp,0x10
11   0x00007fdf6106181e: pop    rbp
12   0x00007fdf6106181f: test   DWORD PTR [rip+0xbc297db],eax        # 0x00007fdf6cc8b000
13                                                 ;   {poll_return}

The interesting lines are marked with '⇒'. As you can see, the add operation is performed over a quad-word (64-bit), using 64-bit registers (rsi).

I also tried to see if byte alignment is an issue by adding a byte-typed padding variable just prior to 'long counter'. The only difference in assembly output was:

before

    0x00007fdf6106180c: movabs r10,0x7d6655660    ;   {oop(a 'java/lang/Class' = 'Counter')}

after

    0x00007fdf6106180c: movabs r10,0x7d6655668    ;   {oop(a 'java/lang/Class' = 'Counter')}

Both addresses are 64-bit aligned and those 'movabs r10, ...' calls are using 64-bit registers.

So far, I've only tested addition. I assume subtraction behaves similarly.
Other operations, such as bitwise operations, assignment, multiplication, etc remain to be tested (or confirmed by somebody sufficiently familiar with HotSpot internals).

Interpreter: Inconclusive

This leaves us with the non-JIT scenario. Let's decompile Compiler.class:

$ javap -c Counter
[...]
static void put(long);
Code:
   0: getstatic     #8                  // Field counter:J
   3: lload_0
   4: ladd
   5: putstatic     #8                  // Field counter:J
   8: return
[...]

...and we will be interested in the 'ladd' bytecode instruction on line #7. However, I've been unable to trace it through to platform-specific implementation so far.

Your help appreciated!

like image 671
nadavwr Avatar asked Aug 07 '14 02:08

nadavwr


People also ask

What versions of OpenJDK does OpenLogic support?

OpenLogic provides and supports free distributions of OpenJDK 8, and free, certified distributions of OpenJDK 11 for Linux, Windows, MacOS, and Docker images. OpenLogic’s certified OpenJDK builds are updated quarterly, with critical security patches on-demand.

What if I don't see the OpenJDK I Need?

Don't see the OpenJDK you need? You can request a custom build or learn more about our support. OpenLogic provides and supports free distributions of OpenJDK 8, and free, certified distributions of OpenJDK 11 for Linux, Windows, MacOS, and Docker images.

What is the difference between Oracle JDK and OpenJDK?

- OpenJDK Community Version OpenJDK is the short form of Open Java Development Kit similar to Oracle's Java Development Kit used to develop Java Applications. Main difference between Oracle JDK and OpenJDK is that OpenJDK is free for all while Oracle Java Development Kit or Oracle JDK requires commercial license.

What are the five concurrency problems that can occur in database?

The five concurrency problems that can occur in database are: (i). Temporary Update Problem (ii). Incorrect Summary Problem (iii). Lost Update Problem (iv). Unrepeatable Read Problem (v). Phantom Read Problem


2 Answers

In fact, you've already answered your own question.

There is no "non-atomic treatment" of double and long on 64-bit HotSpot JVM, because

  1. HotSpot uses 64-bit registers to store 64-bit values (x86_64.ad vs. x86_32.ad).
  2. HotSpot aligns 64-bit fields on 64-bit boundary (universe.inline.hpp)
like image 125
apangin Avatar answered Nov 15 '22 19:11

apangin


https://www.securecoding.cert.org/confluence/display/java/VNA05-J.+Ensure+atomicity+when+reading+and+writing+64-bit+values

VNA05-J. Ensure atomicity when reading and writing 64-bit values

....

VNA05-EX1: This rule can be ignored for platforms that guarantee that 64-bit long and double values are read and written as atomic operations. Note, however, that such guarantees are not portable across different platforms.

The link above discusses this issue in the context of security and seems to suggest that on 64 bit platforms you can indeed assume long assignments to be atomic. 32 bit systems are becoming somewhat rare in server environments, so it is not a strange assumption to make. Do note that the exception is a bit vague on which platforms make this guarantee and doesn't explicitly state that 64 bit openjdk on 64 bit intel is fine for example.

like image 36
Jilles van Gurp Avatar answered Nov 15 '22 17:11

Jilles van Gurp