了解Java中的易失性和非易失性读/写

Man*_*ose 4 java x86 assembly

我的java代码如下

public class MyClass {
    volatile int voltile ;  //7
    int nonVoltile ;   //8

    public static void main(String[] args) {
        for(int i=1; i<100000; i++){
            f();
        }
    }

    static void f(){   //16
        MyClass t = new MyClass();  //17
        t.voltile = t.nonVoltile;  //18

        t.nonVoltile = 0x11111; //20
        t.voltile = 0x22222;  //21

        t.nonVoltile = t.nonVoltile + 1; //23
        t.voltile = t.voltile + 1; //24

    }
}
Run Code Online (Sandbox Code Playgroud)

函数"f"的生成的汇编片段如下

对于易失写

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)
Run Code Online (Sandbox Code Playgroud)

我有以下问题

  1. 为什么两个移动指令上面?为什么不直接将mov 0xc(%esi)移动到0x8(%esi)
  2. 有什么用0x024c7405:lock addl $ 0x0,(%esp)?

对于易失性读取

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)
Run Code Online (Sandbox Code Playgroud)

仅生成单个读取指令

  1. 这个易失性读取是否发生在主存中?
  2. 为什么没有像锁定addl指令锁那样用于易失性写入?

如我所知,我也在粘贴完整的集会

CompilerOracle: print *MyClass.f
Compiled method (c1)     142   14   !         j.assembly.MyClass::f (81 bytes)
 total in heap  [0x024c72c8,0x024c77d8] = 1296
 relocation     [0x024c7398,0x024c73bc] = 36
 main code      [0x024c73c0,0x024c7600] = 576
 stub code      [0x024c7600,0x024c7620] = 32
 oops           [0x024c7620,0x024c7624] = 4
 metadata       [0x024c7624,0x024c7628] = 4
 scopes data    [0x024c7628,0x024c76a4] = 124
 scopes pcs     [0x024c76a4,0x024c77d4] = 304
 dependencies   [0x024c77d4,0x024c77d8] = 4
Loaded disassembler from hsdis-i386.dll
Decoding compiled method 0x024c72c8:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x14830304} 'f' '()V' in 'j/assembly/MyClass'
  #           [sp+0x40]  (sp of caller)
  0x024c73c0: mov    %eax,0xffffc000(%esp)
  0x024c73c7: push   %ebp
  0x024c73c8: sub    $0x38,%esp
  0x024c73cb: mov    $0x14830350,%edx   ;   {metadata('j/assembly/MyClass')}
  0x024c73d0: mov    %fs:0x0,%ecx
  0x024c73d8: mov    0xfffffff4(%ecx),%ecx
  0x024c73db: mov    0x34(%ecx),%eax
  0x024c73de: lea    0x10(%eax),%edi
  0x024c73e1: cmp    0x3c(%ecx),%edi
  0x024c73e4: ja     0x024c7589
  0x024c73ea: mov    %edi,0x34(%ecx)
  0x024c73ed: mov    0x60(%edx),%ecx
  0x024c73f0: mov    %ecx,(%eax)
  0x024c73f2: mov    %edx,0x4(%eax)
  0x024c73f5: xor    %ecx,%ecx
  0x024c73f7: mov    %ecx,0x8(%eax)
  0x024c73fa: mov    %ecx,0xc(%eax)
  0x024c73fd: mov    %eax,%esi          ;*new  ; - j.assembly.MyClass::f@0 (line 17)

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)

  0x024c740a: movl   $0x11111,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@19 (line 20)

  0x024c7411: mov    $0x22222,%eax
  0x024c7416: mov    %eax,0x8(%esi)
  0x024c7419: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@25 (line 21)

  0x024c741e: movl   $0x11112,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@35 (line 23)

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)

  0x024c7428: inc    %eax
  0x024c7429: mov    %eax,0x8(%esi)
  0x024c742c: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@45 (line 24)

  0x024c7431: lea    0x20(%esp),%edi
  0x024c7435: mov    %esi,0x4(%edi)
  0x024c7438: mov    (%esi),%eax
  0x024c743a: mov    %eax,%ebx
  0x024c743c: and    $0x7,%ebx
  0x024c743f: cmp    $0x5,%ebx
  0x024c7442: jne    0x024c74ca
  0x024c7448: mov    %eax,(%edi)
  0x024c744a: mov    0x4(%esi),%ebx
  0x024c744d: mov    0x60(%ebx),%ebx
  0x024c7450: xor    %eax,%ebx
  0x024c7452: mov    %fs:0x0,%eax
  0x024c745a: mov    0xfffffff4(%eax),%eax
  0x024c745d: xor    %ebx,%eax
  0x024c745f: and    $0xffffff87,%eax
  0x024c7462: je     0x024c74eb
  0x024c7468: test   $0x7,%eax
  0x024c746d: jne    0x024c74be
  0x024c746f: test   $0x180,%eax
  0x024c7474: jne    0x024c749a
  0x024c7476: mov    (%edi),%eax
  0x024c7478: and    $0x1ff,%eax
  0x024c747e: mov    %fs:0x0,%ebx
  0x024c7486: mov    0xfffffff4(%ebx),%ebx
  0x024c7489: or     %eax,%ebx
  0x024c748b: lock cmpxchg %ebx,(%esi)
  0x024c748f: jne    0x024c7595
  0x024c7495: jmp    0x024c74eb
  0x024c749a: mov    0x4(%esi),%ebx
  0x024c749d: mov    0x60(%ebx),%ebx
  0x024c74a0: mov    %fs:0x0,%eax
  0x024c74a8: mov    0xfffffff4(%eax),%eax
  0x024c74ab: or     %eax,%ebx
  0x024c74ad: mov    (%edi),%eax
  0x024c74af: lock cmpxchg %ebx,(%esi)
  0x024c74b3: jne    0x024c7595
  0x024c74b9: jmp    0x024c74eb
  0x024c74be: mov    (%edi),%eax
  0x024c74c0: mov    0x4(%esi),%ebx
  0x024c74c3: mov    0x60(%ebx),%ebx
  0x024c74c6: lock cmpxchg %ebx,(%esi)
  0x024c74ca: mov    (%esi),%eax
  0x024c74cc: or     $0x1,%eax
  0x024c74cf: mov    %eax,(%edi)
  0x024c74d1: lock cmpxchg %edi,(%esi)
  0x024c74d5: je     0x024c74eb
  0x024c74db: sub    %esp,%eax
  0x024c74dd: and    $0xfffff003,%eax
  0x024c74e3: mov    %eax,(%edi)
  0x024c74e5: jne    0x024c7595         ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)

  0x024c74eb: mov    0xc(%esi),%eax     ;*getfield nonVoltile
                                        ; - j.assembly.MyClass::f@54 (line 27)

  0x024c74ee: inc    %eax
  0x024c74ef: mov    %eax,0xc(%esi)     ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@59 (line 27)

  0x024c74f2: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@64 (line 28)

  0x024c74f5: inc    %eax
  0x024c74f6: mov    %eax,0x8(%esi)
  0x024c74f9: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@69 (line 28)

  0x024c74fe: lea    0x20(%esp),%eax
  0x024c7502: mov    0x4(%eax),%edi
  0x024c7505: mov    (%edi),%esi
  0x024c7507: and    $0x7,%esi
  0x024c750a: cmp    $0x5,%esi
  0x024c750d: je     0x024c7527
  0x024c7513: mov    (%eax),%esi
  0x024c7515: test   %esi,%esi
  0x024c7517: je     0x024c7527
  0x024c751d: lock cmpxchg %esi,(%edi)
  0x024c7521: jne    0x024c75a6         ;*monitorexit
                                        ; - j.assembly.MyClass::f@73 (line 26)

  0x024c7527: add    $0x38,%esp
  0x024c752a: pop    %ebp
  0x024c752b: test   %eax,0xdd0100      ;   {poll_return}
  0x024c7531: ret                       ;*return
                                        ; - j.assembly.MyClass::f@80 (line 30)

  0x024c7532: mov    %fs:0x0,%esi
  0x024c753a: mov    0xfffffff4(%esi),%esi
  0x024c753d: mov    0x1a4(%esi),%eax
  0x024c7543: movl   $0x0,0x1a4(%esi)
  0x024c754d: movl   $0x0,0x1a8(%esi)
  0x024c7557: mov    %eax,%esi
  0x024c7559: lea    0x20(%esp),%eax
  0x024c755d: mov    0x4(%eax),%ebx
  0x024c7560: mov    (%ebx),%edi
  0x024c7562: and    $0x7,%edi
  0x024c7565: cmp    $0x5,%edi
  0x024c7568: je     0x024c7582
  0x024c756e: mov    (%eax),%edi
  0x024c7570: test   %edi,%edi
  0x024c7572: je     0x024c7582
  0x024c7578: lock cmpxchg %edi,(%ebx)
  0x024c757c: jne    0x024c75b7         ;*monitorexit
                                        ; - j.assembly.MyClass::f@78 (line 26)

  0x024c7582: mov    %esi,%eax
  0x024c7584: jmp    0x024c75ec
  0x024c7589: mov    %edx,%edx
  0x024c758b: call   0x024bc740         ; OopMap{off=464}
                                        ;*new  ; - j.assembly.MyClass::f@0 (line 17)
                                        ;   {runtime_call}
  0x024c7590: jmp    0x024c73fd
  0x024c7595: mov    %esi,0x4(%esp)
  0x024c7599: mov    %edi,(%esp)
  0x024c759c: call   0x024bdc40         ; OopMap{esi=Oop [36]=Oop off=481}
                                        ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)
                                        ;   {runtime_call}
  0x024c75a1: jmp    0x024c74eb
  0x024c75a6: lea    0x20(%esp),%eax
  0x024c75aa: mov    %eax,(%esp)
  0x024c75ad: call   0x024bde00         ;   {runtime_call}
  0x024c75b2: jmp    0x024c7527
  0x024c75b7: lea    0x20(%esp),%eax
  0x024c75bb: mov    %eax,(%esp)
  0x024c75be: call   0x024bde00         ;   {runtime_call}
  0x024c75c3: jmp    0x024c7582
  0x024c75c5: nop    
  0x024c75c6: nop    
  0x024c75c7: mov    %fs:0x0,%esi
  0x024c75cf: mov    0xfffffff4(%esi),%esi
  0x024c75d2: mov    0x1a4(%esi),%eax
  0x024c75d8: movl   $0x0,0x1a4(%esi)
  0x024c75e2: movl   $0x0,0x1a8(%esi)
  0x024c75ec: add    $0x38,%esp
  0x024c75ef: pop    %ebp
  0x024c75f0: jmp    0x024bbec0         ;   {runtime_call}
  0x024c75f5: hlt    
  0x024c75f6: hlt    
  0x024c75f7: hlt    
  0x024c75f8: hlt    
  0x024c75f9: hlt    
  0x024c75fa: hlt    
  0x024c75fb: hlt    
  0x024c75fc: hlt    
  0x024c75fd: hlt    
  0x024c75fe: hlt    
  0x024c75ff: hlt    
[Exception Handler]
[Stub Code]
  0x024c7600: call   0x024bd6c0         ;   {no_reloc}
  0x024c7605: push   $0x77f387fc        ;   {external_word}
  0x024c760a: call   0x024c760f
  0x024c760f: pusha  
  0x024c7610: call   0x77e22130         ;   {runtime_call}
  0x024c7615: hlt    
[Deopt Handler Code]
  0x024c7616: push   $0x24c7616         ;   {section_word}
  0x024c761b: jmp    0x0245c2b0         ;   {runtime_call}
OopMapSet contains 2 OopMaps

#0 
OopMap{off=464}
#1 
OopMap{esi=Oop [36]=Oop off=481}
Picked up _JAVA_OPTIONS: -Djava.net.preferIPv4Stack=true
Java HotSpot(TM) Client VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output
Run Code Online (Sandbox Code Playgroud)

Mar*_*oom 8

您在生成的反汇编中看到的是Java volatile字段的特性.
这个主题不能简单解释,因为它源于CPU的发展和工作方式,但是根据说明简单地说:

 THREAD 0                       THREAD 1
 int x = 0, y = 0;              while (y==0);
 x = 1;                         int z = x, w = y;
 y = x + 1;
Run Code Online (Sandbox Code Playgroud)

THREAD 1很可能以z=0和结束w=2.
这不是你通常想要的.为了更好地理解为什么出现这种情况是指这个伟大的博客或者这个漂亮的文字.

为了解决这个问题,Java定义了一个内存模型,您可以在Java语言规范的第17.4节中阅读更多相关内容.
在该模型中,它被定义为之前称为发生之前的指令之间的关系,并且这种关系确保另一指令之前发生的指令的每个副作用对于后者是可见的.
简单地说,这保证你没有像上面的例子中那样令人讨厌的惊喜,其中,在两个不同的线程中,指令x = 1并且int z = x不在发生在之前的关系中,并且不能保证写入x对另一个线程可见(但是保证它对THREAD 0)是可见的.

第17.4章列出了两个指令处于先发生关系中的原因,例如在同一个线程中.
在这种关系中产生两个指令的另一个条件是进入另一种关系:同步 -关系.
Java volatile字段生成该字段的写入和读取之间的同步.这是一个非常强大的条件!
简单地说:volatile- > synchronize-with - > happen-before - >序列化副作用.

因此,Java volatile是一种可以经常使用而不是锁的同步机制.

为了满足线程之间的先发生关系,必须使用内存屏障.

您编译了IA32体系结构的Java代码,即x86.在这种架构中有各种围栏指令专用于微调.还有原始的序列化指令:这些指令可以保证之前的所有其他指令(包括副作用)在序列化完成之前完成.
他们是穷人的障碍.

  lock addl $0x0,(%esp)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,此指令不执行任何操作,它会向DWORD添加零esp,但它使用lock前缀来执行此操作,前缀是序列化,因此充当内存屏障.说实话,是一个沉重的人.

您的其他问题只是回答:

  • 有两个动作,因为你正在阅读nonVoltile(第一步)和写作voltile(第二步).
  • 易失性读取不需要屏障,因为每次写入voltile都使用一个屏障来序列化写入和读取操作.
  • 如果不考虑缓存层次结构,则会从主内存中发生易失性读取.请注意,您只考虑增加字段的三条指令中的第一条.