当前位置: 首页 > 知识库问答 >
问题:

64位OpenJDK 7/8中并发长写的值完整性保证

晋承运
2023-03-14

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

鉴于以下情况:

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

是否总是保证观察者会遇到由变异线程编写的完整值,或者单词撕裂是一种危险?

此属性适用于32位基本体和64位对象引用,但对于long和double,JLS不保证此属性:

17.7.双精度和长精度的非原子处理:
就Java编程语言内存模型而言,对非易失性长或双精度值的单次写入被视为两次单独的写入:每次写入32位半。这可能导致线程从一次写入中看到64位值的前32位,从另一次写入中看到第二个32位。

但请不要着急:

[...]为了效率起见,此行为是特定于实现的;Java虚拟机的实现可以自由地以原子方式或分两部分执行对长值和双值的写入。鼓励Java虚拟机的实现尽可能避免拆分64位值。[...]

因此,JLS允许JVM实现拆分64位写入,并鼓励开发人员进行相应的调整,但也鼓励JVM实现者坚持使用64位写入。我们还没有最近版本的HotSpot的答案。

由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,因此我尝试分析JIT编译的实际程序集输出。长话短说:需要进一步的测试,但我只能在long上看到原子64位操作。

我使用了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(i);
    System.out.println(counter);
  }

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

我确保始终使用大于MAX\u INT(1e12到1e12 1e5)的值,并重复该操作足够的次数(1e5)以触发JIT。

编译后,我用hdis执行了Counter.main(),如下所示:

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

为计数器生成的程序集。JIT的put()如下所示(为方便起见添加了十进制行号):

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}

有趣的行标有“来”。如您所见,添加操作是使用64位寄存器(rsi)在四字(64位)上执行的。

我还试图通过在“long counter”之前添加一个字节类型的填充变量来查看字节对齐是否存在问题。装配输出的唯一区别是:

之前

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

之后

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

这两个地址都是64位对齐的,并且那些“movabs r10,…”调用使用64位寄存器。

到目前为止,我只测试了加法。我假设减法的行为类似
其他操作,如按位操作、赋值、乘法等仍有待测试(或由足够熟悉热点内部的人员确认)。

这就给我们留下了非JIT场景。让我们html" target="_blank">反编译编译器。类别:

$ 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
[...]

...我们会对第7行的“ladd”字节码指令感兴趣。但是,到目前为止,我还无法将其追溯到特定于平台的实现。

感谢您的帮助!

共有2个答案

昌博易
2023-03-14

https://www.securecoding.cert.org/confluence/display/java/VNA05-J.读取和写入64位值时确保原子性

VNA05-J.读写64位值时确保原子性

....

VNA05-EX1:对于保证64位长双倍值作为原子操作读写的平台,可以忽略此规则。但是,请注意,此类保证不能跨不同平台移植。

上面的链接从安全性的角度讨论了这个问题,似乎表明在64位平台上,您确实可以假设长分配是原子的。32位系统在服务器环境中越来越少见,因此做出这样的假设并不奇怪。请注意,例外情况有点含糊不清,在哪些平台上可以保证这一点,例如,没有明确指出64位intel上的64位openjdk是可以的。

姜泰宁
2023-03-14

事实上,你已经回答了自己的问题。

在64位HotSpot JVM上没有“非原子处理”double和long,因为

  1. HotSpot使用64位寄存器存储64位值(x86\u 64.ad与x86\u 32.ad)
 类似资料:
  • 问题内容: 在python中,我得到了一个64位整数。该整数是通过采用几个不同的8位整数并将它们混搭为一个64位巨型整数而创建的。再次将它们分开是我的工作。 例如: 所以我想做的是获取我的源代码并返回一个长度为8的数组,其中数组中的每个int都是上面列出的int。 我打算使用,但老实说,阅读文档并不清楚我将如何实现。 问题答案: 在Python 2.x中,返回一个字节字符串。将其转换为整数数组很容

  • 如果我在数据库中存储一个散列值,但被散列的原始值的长度是固定的(例如,总是4个字符),这会影响散列函数的单向性吗? 更准确地说,我有敏感字符串,然后加密并存储在数据库中。为了搜索这些字符串,我不想解密数据库中的每个条目,所以我还将字符串前4个字符的散列存储在另一列中。当我想搜索数据库时,我会生成搜索词的前4个字符的散列,并将其与存储的散列进行比较,以找到匹配或可以匹配的条目,然后解密这些条目以检查

  • 问题内容: 在Java中,是否保证int始终为32位,而长为64位,而不管体系结构是32位还是64位? 问题答案: Java是平台无关的。所以是32位,并且是64位的。

  • 问题内容: 针对32位JDK构建和编译为32位字节代码的Java代码是否可以在64位JVM中工作?还是64位JVM需要64位字节代码? 为了提供更多细节,我有在运行32位JVM的Solaris环境中工作的代码,但是现在将JDK和Weblogic Server升级到64位后遇到了问题。 问题答案: 是的,假设您使用平台无关的库,则Java字节码(和源代码)是平台无关的。32与64位无关紧要。

  • 问题内容: 本机整数算术指令是否比其计数器部件慢(在装有OS的计算机上)? 编辑:在当前CPU上,例如Intel Core2 Duo,i5 / i7等。 问题答案: 这取决于确切的CPU和操作。例如,在64位Pentium IV上,64位寄存器的乘法要慢得多。Core 2和更高版本的CPU从一开始就设计用于64位操作。 通常,即使是为64位平台编写的代码也使用32位变量,其中的值将适合它们。这主要

  • 我运行Apache 2.4与PHP 5.4.9在Windows 8 64位。尝试为PHP启用cURL扩展,但它没有加载。 我曾经尝试过: 在php中未注释模块加载的行。ini(是,正确的ini文件) 问题出在DLL文件上吗?在哪里可以找到与我的设置相关的内容? 提前感谢!