Ensuring integrity integrity for simultaneous long-term recording in 64-bit OpenJDK 7/8

Note: this question is not related to volatile, AtomicLong or any perceived flaw in the described use case.

The property that I am trying to prove or exclude is as follows:

Given the following:

  • recent 64-bit OpenJDK 7/8 (preferably 7, but 8 is also useful).
  • Intel-base multiprocessor system
  • non-volatile long primitive variable
  • multiple unsynchronized mutator threads
  • unsynchronized stream of observers

Is it always observed that the observer always encounters intact values ​​recorded by the mutator stream, or is this word breaking the danger?

JLS: Inconclusive

This property exists for 32-bit primitives and 64-bit object references, but JLS is not guaranteed for lengths and doubles:

17.7. Non-atomic processing of double and long:
For the purposes of the memory model of the Java programming language, one record in a non-volatile long or double value is considered as two separate records: one for each 32-bit half. This can lead to a situation where the stream sees the first 32 bits of a 64-bit value from one record and the second 32 bits from another record.

But hold the horses:

[...] For efficiency, this behavior is implementation-specific; An implementation of a Java virtual machine can write to long and double values ​​atomically or in two parts. Implementations of the Java virtual machine are recommended to avoid splitting 64-bit values ​​where possible. [...]

Thus, JLS allows JVM implementations to separate 64-bit entries and encourages developers to adjust them accordingly, but also encourages JVM developers to stick to 64-bit entries. We do not have an answer to the latest HotSpot versions yet.

HotSpot JIT: Careful Optimism

Since word tearing most likely occurs within the boundaries of hard loops and other hot spots, I tried to analyze the actual data collection from the JIT compilation. In short: further testing is necessary, but I can only see atomic 64-bit operations on longs.

I used hdis , a disassembler plugin for OpenJDK. Having created and installed the plugin in my old build of OpenJDK 7u25, I started writing 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 always used values ​​greater than MAX_INT (from 1e12 to 1e12 + 1e5) and repeated the operation enough times (1e5) to run JIT.

After compiling, I executed Counter.main () using hdis, for example:

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

The assembly generated for Counter.put () 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} 

Interesting lines are marked with "⇒". As you can see, the add operation is performed on a quad-core (64-bit) using 64-bit registers ( rsi ).

I also tried to find out if byte alignment is a problem by adding a padding variable for the byte immediately before the "long counter". The only difference in the assembly output:

front

  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 calls to "movabs r10, ..." use 64-bit registers.

So far I have only tested the add-on. I guess subtraction behaves the same.
Other operations, such as bitwise operations, assignment, multiplication, etc., remains to be verified (or confirmed by someone familiar with the internal components of HotSpot).

Interpreter: Non-Convertible

This leaves us with a non-JIT script. Let 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 in line # 7. However, I have not been able to trace it to before implementation on the platform so far.

Your help was appreciated!

+6
source share
2 answers

In fact, you have already answered your question.

There is no "non-atomic processing" of double and long in the 64-bit JVM HotSpot , because

+3
source

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 ensure that 64-bit long and double values ​​are read and written as atomic operations. Please note, however, that such warranties do not carry across platforms.

The link above discusses this issue in the security context and it seems to suggest that on 64-bit platforms you can assume that long assignments are atomic. 32-bit systems are becoming somewhat rare in server environments, so this is not a strange assumption. Note that the exception is a bit vague on which platforms it guarantees and does not explicitly indicate that the 64-bit openjdk on the 64-bit intel is fine, for example.

+2
source

Source: https://habr.com/ru/post/973469/


All Articles