64位OpenJDK 7/8中并发长写的值完整性保证 [英] Value integrity guarantee for concurrent long writes in 64-bit OpenJDK 7/8

查看:225
本文介绍了64位OpenJDK 7/8中并发长写的值完整性保证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:这个问题与volatile,AtomicLong或所描述用例中的任何感觉到的缺陷无关。



我试图证明或规则的属性出价如下:




如下:




  • 最近的64位OpenJDK 7/8(最好是7,但也有帮助)

  • 基于英特尔的多处理系统

  • a非易失性长原始变量

  • 多个不同步的变异程序线程

  • 不同步的观察者线程



观察者总是保证会遇到由mutator线程写的完整值,或者是字撕裂危险?




JLS:不确定



此属性适用于32位原语和64位对象引用,但JLS不保证长整数和双精度:


17.7。非原子处理double和long:

对于Java编程语言内存模型,对非易失性long或double值的单次写入被视为两次单独写入:一个到每个32位的一半。这可能导致一个线程看到一个写入的64位值的前32位,而另一个写入的第二个32位。


但是抱着你的马:


[...]出于效率的考虑, Java虚拟机的实现可以自由地以原子方式或两部分执行对长和双值的写入。鼓励Java虚拟机的实现避免在可能的情况下拆分64位值。 [...]


因此,JLS 允许JVM实现拆分64位写入, 鼓励开发人员进行相应调整,同时鼓励 JVM实施者坚持使用64位写入。



HotSpot JIT:小心的乐观



由于字撕裂最可能发生在紧环和其他热点的范围内,我试图分析从JIT编译的实际汇编输出。



我使用了 /github.com/drazzib/openjdk-hsdisrel =nofollow> hdis ,一个用于OpenJDK的反汇编插件。
在我的老化OpenJDK 7u25版本中构建并安装了插件后,我开始写一个简短的程序:

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

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



我确保始终使用大于MAX_INT 1e12到1e12 + 1e5),并重复该操作足够多的时间(1e5)来触发JIT。



编译后,我用hdis执行Counter.main :

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

.put()by JIT如下(为了方便,添加了十进制行号):

  01#{method} ''(J)V'in'Counter'
02⇒#parm0:rsi:rsi = long
03#[sp + 0x20](sp调用者)
04 0x00007fdf61061800:sub rsp ,0x18
05 0x00007fdf61061807:mov QWORD PTR [rsp + 0x10],rbp; *同步条目
06; - Counter :: put @ -1(line 15)
07 0x00007fdf6106180c:movabs r10,0x7d6655660; {oop('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}

有趣的行标有⇒。
可以看到,使用64位寄存器( rsi )。



我还试图通过添加字节类型的填充来查看字节对齐是否是一个问题变量就在长期柜台之前。汇编输出中的唯一区别是:

之前

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



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

位对齐,而'movabs r10,...'调用使用64位寄存器。



到目前为止,我只测试了添加。我假设减法的行为类似。

其他操作,例如位操作,赋值,乘法等仍然需要测试(或者由熟悉HotSpot内部的人来确认)。



解释器:不确定



这使我们无法使用非JIT场景。让我们反编译Compiler.class:

  $ javap -c Counter 
[...] put(long);
代码:
0:getstatic#8 //字段计数器:J
3:lload_0
4:ladd
5:putstatic#8 //字段计数器:J
8:return
[...]

...将对第#7行的'ladd'字节码指令感兴趣。
但是,我无法跟踪

其实你已经回答了你自己的问题。



没有非原子治疗 c $ c> double 和 long (64位HotSpot JVM ),因为


  1. HotSpot使用64位寄存器来存储64位值( x86_64.ad x86_32.ad )。

  2. HotSpot对齐64位边界上的64位字段( universe.inline.hpp


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!

解决方案

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)

这篇关于64位OpenJDK 7/8中并发长写的值完整性保证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆