JMM - Java 內(nèi)存模型

JMM定義

JMM 即 Java Memory Model荧呐,也叫 Java 內(nèi)存模型。JMM 就是一種規(guī)范惑申,它定義了什么情況開發(fā)者不需要去感知計(jì)算機(jī)的各種重排序歧寺,什么情況需要開發(fā)者去干涉重排序,以保證程序的執(zhí)行結(jié)果可預(yù)測(cè)揣云。

JMM的由來(lái)

計(jì)算機(jī)這么多年來(lái)整體運(yùn)行速度不斷地提升捕儒,除了像CPU時(shí)鐘頻率、內(nèi)存讀寫速度等硬件性能不斷提升之外邓夕,還要?dú)w功于計(jì)算機(jī)科學(xué)家對(duì)于計(jì)算機(jī)對(duì)于各種指令處理效率的不斷優(yōu)化刘莹,包括超標(biāo)量流水線技術(shù),動(dòng)態(tài)指令調(diào)度焚刚,猜測(cè)執(zhí)行点弯,多級(jí)緩存技術(shù)等。在這其中矿咕,允許重排序?qū)τ谟?jì)算機(jī)運(yùn)行效率的提升產(chǎn)生了重要的作用抢肛,但同時(shí)也帶來(lái)了一些問題。計(jì)算機(jī)只能確保單線程情況下重排序?qū)τ谶\(yùn)行結(jié)果沒有影響碳柱,對(duì)于多線程就無(wú)能為力了捡絮。這個(gè)時(shí)候就需要一個(gè)規(guī)范來(lái)保證開發(fā)者既能享受重排序帶來(lái)的性能的提升又能讓復(fù)雜情況下的運(yùn)行結(jié)果可控,JMM 就是這樣一個(gè)規(guī)范莲镣。JMM 規(guī)定了 JVM 必須遵循的一組最小保證福稳,這組保證規(guī)定了對(duì)變量的操作何時(shí)對(duì)其他線程可見。換句話說(shuō)瑞侮,JMM 對(duì)內(nèi)存可見性作出了一些承諾的圆,在承諾之外鼓拧,開發(fā)者需要自己去處理內(nèi)存可見性問題。

內(nèi)存可見性問題

上面提到了內(nèi)存可見性問題略板,那么毁枯,什么是內(nèi)存可見性問題。

內(nèi)存可見性問題的核心是 CPU 的緩存與主內(nèi)存不一致叮称。

那么,這里就涉及到計(jì)算機(jī)原理的部分知識(shí)藐鹤,下圖是 X86 架構(gòu)下 CPU 緩存的布局:

CPU架構(gòu)圖.png

從圖中可以看出 CPU 有多級(jí)緩存瓤檐,每個(gè)核心的一二級(jí)緩存數(shù)據(jù)都是該 CPU 核心私有的,由于有緩存一致性協(xié)議(例如 MESI )的存在娱节,各個(gè)核心的緩存之間不會(huì)存在不同步的問題挠蛉。

這里簡(jiǎn)單講一下緩存一致性協(xié)議 MESI,當(dāng)各個(gè) CPU 核心都緩存了一個(gè)共享變量時(shí)肄满,有任何一個(gè)核心對(duì)它作出了修改都會(huì)讓其他核心內(nèi)對(duì)應(yīng)變量的緩存單元失斍垂拧(這里失效的是整個(gè) CacheLine,不僅僅是變量所占用的區(qū)域)并且把修改值同步到主內(nèi)存稠歉。其他核心如果后續(xù)要操作這個(gè)變量掰担,必須從主內(nèi)存讀,這樣就可以保證各個(gè)緩存的一致性怒炸。

但引入緩存一致性協(xié)議會(huì)有很大的性能損耗带饱,為了解決這個(gè)問題,又進(jìn)行了各種優(yōu)化阅羹,這其中就有在計(jì)算單元和一級(jí)緩存之間引入 StoreBuffer 和 LoadBuffer 勺疼,如下圖所示:

加入各種Buffer的CPU架構(gòu)圖

StoreBuffer 和 LoadBuffer 的引入,大大提升了計(jì)算機(jī)性能捏鱼,但同時(shí)也帶來(lái)了一些問題:各級(jí)緩存之間數(shù)據(jù)是一致的执庐,但 StoreBuffer 和 LoadBuffer 一級(jí)緩存之間的數(shù)據(jù)卻是異步的,這里就會(huì)存在一致性問題导梆。

當(dāng)一個(gè)緩存中的數(shù)據(jù)被修改后轨淌,會(huì)存到 StoreBuffer 中,而 StoreBuffer 不會(huì)立即把修改后的數(shù)據(jù)同步到主內(nèi)存问潭,這時(shí)其他核心在主內(nèi)存中讀取到就是舊數(shù)據(jù)猿诸,也就是說(shuō)一個(gè)數(shù)據(jù)在一個(gè)核心的寫操作會(huì)出現(xiàn)對(duì)其他核心不可見的情況,這就是內(nèi)存可見性問題狡忙。

重排序

上面講的內(nèi)存可見性問題其本質(zhì)就是 CPU 內(nèi)存重排序梳虽,它是重排序的一種。這里講一下什么是重排序灾茁。

重排序分為三種:編譯重排序窜觉、CPU 指令重排序和 CPU 內(nèi)存重排序谷炸。

  • 編譯器重排序:對(duì)于沒有先后依賴的語(yǔ)句,編譯器可以重新調(diào)整語(yǔ)句的順序禀挫;
  • CPU 指令重排序:對(duì)于沒有先后依賴的指令并行執(zhí)行旬陡;
  • CPU 內(nèi)存重排序:CPU 有自己的緩存,指令的執(zhí)行順序與寫入主內(nèi)存的順序不一定一致语婴。

編譯器重排序?qū)﹂_發(fā)者來(lái)說(shuō)是無(wú)感知的描孟,我們主要關(guān)注的是 CPU 指令重排序和 CPU 內(nèi)存重排序,這兩者都會(huì)對(duì)運(yùn)行結(jié)果產(chǎn)生影響砰左。

舉個(gè)例子:假如有 X匿醒,Y,a缠导,b 四個(gè)共享變量廉羔,我們?cè)趦蓚€(gè)不同的線程分別執(zhí)行下面的代碼:

線程一:

X = 1;
a = Y;

線程二:

Y = 1;
b = X;

這兩個(gè)線程的執(zhí)行順序是不一定的,有可能是順序執(zhí)行僻造,也可能是交叉執(zhí)行憋他,最終結(jié)果可能是:

  • a = 0, b = 1 (線程一執(zhí)行 -> 線程二執(zhí)行)
  • b = 0, a = 1 (線程二執(zhí)行 -> 線程一執(zhí)行)
  • a = 1, b = 1 (兩個(gè)線程交叉執(zhí)行)

上面就是 CPU 指令重排序產(chǎn)生的影響。但實(shí)際情況會(huì)有第四種結(jié)果:

  • a = 0, b = 0 (內(nèi)存重排序)

導(dǎo)致這個(gè)結(jié)果的原因是兩個(gè)線程全部或其中一個(gè)的寫入操作沒有同步到主內(nèi)存中髓削,因此給 a 或 b 賦值時(shí)讀取到的還是舊值 0竹挡,這就是內(nèi)存可見性問題。

CPU 指令重排序問題我們可以通過(guò)鎖蔬螟、CAS 等同步機(jī)制來(lái)解決此迅,編譯器重排序和 CPU 內(nèi)存重排序都可以通過(guò)引入內(nèi)存屏障來(lái)解決,這里主要關(guān)注內(nèi)存屏障在 CPU 重排序的應(yīng)用旧巾。

內(nèi)存屏障

內(nèi)存屏障是一個(gè)比較底層的概念耸序,它能對(duì)重排序作一定的限制,不同的內(nèi)存屏障對(duì)重排序限制不同鲁猩,一般都是組合使用的坎怪。作為 Java 開發(fā)者我們知道使用 volatile 關(guān)鍵字修飾的變量不會(huì)存在內(nèi)存可見性問題,它的原理其實(shí)就是在對(duì)變量的操作前后都加入了兩個(gè)不同的內(nèi)存屏障廓握,以保證所有的讀寫組合都不會(huì)發(fā)生內(nèi)存可見性問題搅窿。

可以把內(nèi)存屏障分為四類:

  • LoadLoad:禁止讀和讀的重排序
  • StoreStore:禁止寫和寫的重排序
  • LoadStore:禁止讀和寫的重排序
  • StoreLoad:禁止寫和讀的重排序

JDK 8 開始,Unsafe 類提供了三個(gè)內(nèi)存屏障方法:

public final class Unsafe { 
    // ...
    public native void loadFence(); 
    public native void storeFence(); 
    public native void fullFence(); 
    // ...
}

這三個(gè)方法對(duì)應(yīng)的內(nèi)存屏障如下:

  • loadFence = LoadLoad + LoadStore
  • storeFence = StoreStore + LoadStore
  • fullFence = loadFence + storeFence + StoreLoad

我們平常在開發(fā)中一般不會(huì)去主動(dòng)使用內(nèi)存屏障隙券,而內(nèi)存屏障所實(shí)現(xiàn)的效果可以用 happen-before 來(lái)描述男应。

happen-before

首先來(lái)說(shuō)說(shuō)什么是 happen-before:它用來(lái)描述來(lái)個(gè)操作之間的內(nèi)存可見性,如果 A 操作 happen-before 于 B 操作娱仔,那么 A 操作的執(zhí)行結(jié)果必須是對(duì) B 操作可見的沐飘,這里隱含了一個(gè)條件,只有在 A 操作的執(zhí)行實(shí)際發(fā)生在 B 操作之前,這個(gè)可見性保證才會(huì)有效耐朴,happen-before 并不會(huì)去改變 A 和 B 的執(zhí)行順序借卧。

JMM 規(guī)范借助 happen-before 可以更好的描述出來(lái)。

happen-before 有以下四個(gè)基本規(guī)則:

  • 單線程中的每個(gè)操作筛峭,happen-before于該線程中任意后續(xù)操作铐刘。
  • 對(duì)volatile變量的寫,happen-before于后續(xù)對(duì)這個(gè)變量的讀影晓。
  • 對(duì)synchronized的解鎖镰吵,happen-before于后續(xù)對(duì)這個(gè)鎖的加鎖。
  • 對(duì)final變量的寫挂签,happen-before于final域?qū)ο蟮淖x捡遍,happen-before于后續(xù)對(duì)final變量的讀。

除了以上四個(gè)基礎(chǔ)規(guī)則之外竹握,happen-before 還具有傳遞性。傳遞性是指當(dāng) A happen-before 于 B辆飘,B happen-before 于 C 啦辐,那么操作 A 的結(jié)果一定對(duì)操作 C 可見。

這四個(gè)基本規(guī)則再加上 happen-before 的傳遞性蜈项,就構(gòu)成了 JMM 對(duì)開發(fā)者的整個(gè)承諾芹关。在這個(gè)承諾之后的部分,開發(fā)者就需要小心處理內(nèi)存可見性問題紧卒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侥衬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子跑芳,更是在濱河造成了極大的恐慌轴总,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件博个,死亡現(xiàn)場(chǎng)離奇詭異怀樟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盆佣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門往堡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人共耍,你說(shuō)我怎么就攤上這事虑灰。” “怎么了痹兜?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵穆咐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我佃蚜,道長(zhǎng)庸娱,這世上最難降的妖魔是什么着绊? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮熟尉,結(jié)果婚禮上归露,老公的妹妹穿的比我還像新娘。我一直安慰自己斤儿,他們只是感情好剧包,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著往果,像睡著了一般疆液。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陕贮,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天堕油,我揣著相機(jī)與錄音,去河邊找鬼肮之。 笑死掉缺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戈擒。 我是一名探鬼主播眶明,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筐高!你這毒婦竟也來(lái)了搜囱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柑土,失蹤者是張志新(化名)和其女友劉穎蜀肘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冰单,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幌缝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诫欠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涵卵。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荒叼,靈堂內(nèi)的尸體忽然破棺而出轿偎,到底是詐尸還是另有隱情,我是刑警寧澤被廓,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布坏晦,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昆婿。R本人自食惡果不足惜球碉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仓蛆。 院中可真熱鬧睁冬,春花似錦、人聲如沸看疙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)能庆。三九已至施禾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搁胆,已是汗流浹背弥搞。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渠旁,地道東北人拓巧。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像一死,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傻唾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容