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

我的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 } } 

函数“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) 

我有以下问题

  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) 

仅生成单个读取指令

  1. 这个易失性读取是否发生在主内存中?
  2. 为什么没有像lock 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 

您在生成的反汇编中看到的是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; 

线程1很可能以z=0w=2
这不是你通常想要的。 为了更好地理解为什么会发生这种情况,请参考这个伟大的博

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

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

所以Java volatile是一种经常使用的机制,而不是锁。

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

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

  lock addl $0x0,(%esp) 

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

您的其他问题只是回答:

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