摘要:為了實(shí)現(xiàn)的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序。上述寫(xiě)和讀的內(nèi)存屏障插入策略非常保守。
本講座地址https://segmentfault.com/l/15... 歡迎大家圍觀
Java的Volatile的特征是任何讀都能讀到最新值,本質(zhì)上是JVM通過(guò)內(nèi)存屏障來(lái)實(shí)現(xiàn)的,讓我們看看從字節(jié)碼以及匯編碼的角度,來(lái)看下是否真是如此?
一 Volatile與內(nèi)存屏障本節(jié)內(nèi)容來(lái)自:http://www.infoq.com/cn/artic...
為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,JMM會(huì)分別限制重排序類(lèi)型。下面是JMM針對(duì)編譯器制定的volatile重排序規(guī)則表:
舉例來(lái)說(shuō),第三行最后一個(gè)單元格的意思是:在程序順序中,當(dāng)?shù)谝粋€(gè)操作為普通變量的讀或?qū)憰r(shí),如果第二個(gè)操作為volatile寫(xiě),則編譯器不能重排序這兩個(gè)操作。
從上表我們可以看出:
當(dāng)?shù)诙€(gè)操作是volatile寫(xiě)時(shí),不管第一個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile寫(xiě)之前的操作不會(huì)被編譯器重排序到volatile寫(xiě)之后。
當(dāng)?shù)谝粋€(gè)操作是volatile讀時(shí),不管第二個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile讀之后的操作不會(huì)被編譯器重排序到volatile讀之前。
當(dāng)?shù)谝粋€(gè)操作是volatile寫(xiě),第二個(gè)操作是volatile讀時(shí),不能重排序。
為了實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序。對(duì)于編譯器來(lái)說(shuō),發(fā)現(xiàn)一個(gè)最優(yōu)布置來(lái)最小化插入屏障的總數(shù)幾乎不可能,為此,JMM采取保守策略。下面是基于保守策略的JMM內(nèi)存屏障插入策略:
在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障。
在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。
上述內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺(tái),任意的程序中都能得到正確的volatile內(nèi)存語(yǔ)義。
下面是保守策略下,volatile寫(xiě)插入內(nèi)存屏障后生成的指令序列示意圖:
上圖中的StoreStore屏障可以保證在volatile寫(xiě)之前,其前面的所有普通寫(xiě)操作已經(jīng)對(duì)任意處理器可見(jiàn)了。這是因?yàn)镾toreStore屏障將保障上面所有的普通寫(xiě)在volatile寫(xiě)之前刷新到主內(nèi)存。
這里比較有意思的是volatile寫(xiě)后面的StoreLoad屏障。這個(gè)屏障的作用是避免volatile寫(xiě)與后面可能有的volatile讀/寫(xiě)操作重排序。因?yàn)榫幾g器常常無(wú)法準(zhǔn)確判斷在一個(gè)volatile寫(xiě)的后面,是否需要插入一個(gè)StoreLoad屏障(比如,一個(gè)volatile寫(xiě)之后方法立即return)。為了保證能正確實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,JMM在這里采取了保守策略:在每個(gè)volatile寫(xiě)的后面或在每個(gè)volatile讀的前面插入一個(gè)StoreLoad屏障。從整體執(zhí)行效率的角度考慮,JMM選擇了在每個(gè)volatile寫(xiě)的后面插入一個(gè)StoreLoad屏障。因?yàn)関olatile寫(xiě)-讀內(nèi)存語(yǔ)義的常見(jiàn)使用模式是:一個(gè)寫(xiě)線(xiàn)程寫(xiě)volatile變量,多個(gè)讀線(xiàn)程讀同一個(gè)volatile變量。當(dāng)讀線(xiàn)程的數(shù)量大大超過(guò)寫(xiě)線(xiàn)程時(shí),選擇在volatile寫(xiě)之后插入StoreLoad屏障將帶來(lái)可觀的執(zhí)行效率的提升。從這里我們可以看到JMM在實(shí)現(xiàn)上的一個(gè)特點(diǎn):首先確保正確性,然后再去追求執(zhí)行效率。
下面是在保守策略下,volatile讀插入內(nèi)存屏障后生成的指令序列示意圖:
上圖中的LoadLoad屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通寫(xiě)重排序。
上述volatile寫(xiě)和volatile讀的內(nèi)存屏障插入策略非常保守。在實(shí)際執(zhí)行時(shí),只要不改變volatile寫(xiě)-讀的內(nèi)存語(yǔ)義,編譯器可以根據(jù)具體情況省略不必要的屏障。下面我們通過(guò)具體的示例代碼來(lái)說(shuō)明:
class VolatileBarrierExample { int a; volatile int v1 = 1; volatile int v2 = 2; void readAndWrite() { int i = v1; //第一個(gè)volatile讀 int j = v2; // 第二個(gè)volatile讀 a = i + j; //普通寫(xiě) v1 = i + 1; // 第一個(gè)volatile寫(xiě) v2 = j * 2; //第二個(gè) volatile寫(xiě) } … //其他方法 }
針對(duì)readAndWrite()方法,編譯器在生成字節(jié)碼時(shí)可以做如下的優(yōu)化:
注意,最后的StoreLoad屏障不能省略。因?yàn)榈诙€(gè)volatile寫(xiě)之后,方法立即return。此時(shí)編譯器可能無(wú)法準(zhǔn)確斷定后面是否會(huì)有volatile讀或?qū)懀瑸榱税踩鹨?jiàn),編譯器常常會(huì)在這里插入一個(gè)StoreLoad屏障。
上面的優(yōu)化是針對(duì)任意處理器平臺(tái),由于不同的處理器有不同“松緊度”的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化。以x86處理器為例,上圖中除最后的StoreLoad屏障外,其它的屏障都會(huì)被省略。
前面保守策略下的volatile讀和寫(xiě),在 x86處理器平臺(tái)可以?xún)?yōu)化成:
前文提到過(guò),x86處理器僅會(huì)對(duì)寫(xiě)-讀操作做重排序。X86不會(huì)對(duì)讀-讀,讀-寫(xiě)和寫(xiě)-寫(xiě)操作做重排序,因此在x86處理器中會(huì)省略掉這三種操作類(lèi)型對(duì)應(yīng)的內(nèi)存屏障。在x86中,JMM僅需在volatile寫(xiě)后面插入一個(gè)StoreLoad屏障即可正確實(shí)現(xiàn)volatile寫(xiě)-讀的內(nèi)存語(yǔ)義。這意味著在x86處理器中,volatile寫(xiě)的開(kāi)銷(xiāo)比volatile讀的開(kāi)銷(xiāo)會(huì)大很多(因?yàn)閳?zhí)行StoreLoad屏障開(kāi)銷(xiāo)會(huì)比較大)。
二 Volatile的字節(jié)碼為了搞清楚內(nèi)存屏障,我們扒開(kāi)class字節(jié)碼看一下,用javap -v -p class文件名(不要.class 后綴)運(yùn)行
volatile int v1; descriptor: I flags: ACC_VOLATILE ..... void readAndWrite(); descriptor: ()V flags: Code: stack=3, locals=3, args_size=1 0: aload_0 1: getfield #52 // Field v1:I 4: istore_1 5: aload_0 6: getfield #54 // Field v2:I 9: istore_2 10: aload_0 11: iload_1 12: iload_2 13: iadd 14: putfield #72 // Field a:I 17: aload_0 18: iload_1 19: iconst_1 20: isub 21: putfield #52 // Field v1:I 24: aload_0 25: iload_2 26: iload_1 27: imul 28: putfield #54 // Field v2:I 31: return
除了其變量定義的時(shí)候有一個(gè)Volatile外,之后的字節(jié)碼跟有無(wú)Volatile完全一樣,于是我們又扒了下匯編代碼
三 Volatile的匯編碼為了看到匯編碼,要使用hsdis插件, 在mac系統(tǒng)下需要安裝一個(gè)hsdis-amd64.dylib的插件。在網(wǎng)上找了一個(gè),地址在這里。
下載下來(lái)后,將其放置到你的jre lib目錄下即可。
mac系統(tǒng)上命令如下,
sudo mv ./hsdis-amd64.dylib /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib
然后再運(yùn)行
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*VolatileBarrierExample.readAndWrite -XX:CompileCommand=compileonly,*VolatileBarrierExample.readAndWrite com.earnfish.VolatileBarrierExample > out.put
其中*VolatileBarrierExample.readAndWrite表示你運(yùn)行的類(lèi).函數(shù), com.earnfish.VolatileBarrierExample表示你的包名.類(lèi)名,注意需要有main函數(shù)來(lái)運(yùn)行你所要執(zhí)行的函數(shù)。得出匯編碼如下
0x000000011214bb49: mov %rdi,%rax 0x000000011214bb4c: dec %eax 0x000000011214bb4e: mov %eax,0x10(%rsi) 0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1 ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35) 0x000000011214bb56: imul %edi,%ebx 0x000000011214bb59: mov %ebx,0x14(%rsi) 0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2 ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36)
其對(duì)應(yīng)的Java代碼如下
v1 = i - 1; // 第一個(gè)volatile寫(xiě) v2 = j * i; // 第二個(gè)volatile寫(xiě)
可見(jiàn)其本質(zhì)是通過(guò)一個(gè)lock指令來(lái)實(shí)現(xiàn)的。那么lock是什么意思呢?
查詢(xún)IA32手冊(cè),它的作用是使得本CPU的Cache寫(xiě)入了內(nèi)存,該寫(xiě)入動(dòng)作也會(huì)引起別的CPU invalidate其Cache。所以通過(guò)這樣一個(gè)空操作,可讓前面volatile變量的修改對(duì)其他CPU立即可見(jiàn)。
所以,它的作用是
鎖住主存
任何讀必須在寫(xiě)完成之后再執(zhí)行
使其它線(xiàn)程這個(gè)值的棧緩存失效
類(lèi)似于前面是storestore,后面是storeload
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/69025.html
摘要:內(nèi)存語(yǔ)義的的實(shí)現(xiàn)可見(jiàn)性的實(shí)現(xiàn)基于的讀取,寫(xiě)入兩個(gè)操作的內(nèi)存語(yǔ)義。首先,對(duì)中內(nèi)存屏障的介紹內(nèi)存屏障用于控制特定條件下的重排序和內(nèi)存可見(jiàn)性問(wèn)題。在大多數(shù)處理器的實(shí)現(xiàn)中,這個(gè)屏障是個(gè)萬(wàn)能屏障,兼具其它三種內(nèi)存屏障的功能。 volatile,可見(jiàn)性,有序性 volatile的特性 可見(jiàn)性:對(duì)一個(gè)volatile變量的讀,總能獲取其他任意線(xiàn)程對(duì)該變量最后的寫(xiě)入。 有序性:JMM會(huì)限制volat...
摘要:文章簡(jiǎn)介分析的作用以及底層實(shí)現(xiàn)原理,這也是大公司喜歡問(wèn)的問(wèn)題內(nèi)容導(dǎo)航的作用什么是可見(jiàn)性源碼分析的作用在多線(xiàn)程中,和都起到非常重要的作用,是通過(guò)加鎖來(lái)實(shí)現(xiàn)線(xiàn)程的安全性。而的主要作用是在多處理器開(kāi)發(fā)中保證共享變量對(duì)于多線(xiàn)程的可見(jiàn)性。 文章簡(jiǎn)介 分析volatile的作用以及底層實(shí)現(xiàn)原理,這也是大公司喜歡問(wèn)的問(wèn)題 內(nèi)容導(dǎo)航 volatile的作用 什么是可見(jiàn)性 volatile源碼分析 ...
摘要:內(nèi)存模型基本概念計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在中執(zhí)行的,而執(zhí)行指令過(guò)程中,勢(shì)必涉及到數(shù)據(jù)的讀取和寫(xiě)入。有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。 內(nèi)存模型基本概念 計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過(guò)程中,勢(shì)必涉及到數(shù)據(jù)的讀取和寫(xiě)入。由于程序運(yùn)行過(guò)程中的臨時(shí)數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的,這時(shí)就存在一個(gè)問(wèn)題,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)...
摘要:一言以蔽之,被修飾的變量能夠保證每個(gè)線(xiàn)程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。為了實(shí)現(xiàn)內(nèi)存語(yǔ)義時(shí),編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序。volatile原理volatile簡(jiǎn)介Java內(nèi)存模型告訴我們,各個(gè)線(xiàn)程會(huì)將共享變量從主內(nèi)存中拷貝到工作內(nèi)存,然后執(zhí)行引擎會(huì)基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。 線(xiàn)程在工作內(nèi)存進(jìn)行操作后何時(shí)會(huì)寫(xiě)到主內(nèi)存中...
摘要:一言以蔽之,被修飾的變量能夠保證每個(gè)線(xiàn)程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。為了實(shí)現(xiàn)內(nèi)存語(yǔ)義時(shí),編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序。volatile原理volatile簡(jiǎn)介Java內(nèi)存模型告訴我們,各個(gè)線(xiàn)程會(huì)將共享變量從主內(nèi)存中拷貝到工作內(nèi)存,然后執(zhí)行引擎會(huì)基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。 線(xiàn)程在工作內(nèi)存進(jìn)行操作后何時(shí)會(huì)寫(xiě)到主內(nèi)存中...
閱讀 1858·2021-11-22 15:25
閱讀 3958·2021-11-17 09:33
閱讀 2526·2021-10-12 10:12
閱讀 1813·2021-10-09 09:44
閱讀 3243·2021-10-08 10:04
閱讀 1326·2021-09-29 09:35
閱讀 1960·2019-08-30 12:57
閱讀 1313·2019-08-29 16:22