阿裏P8架構師詳談 Java 内存模型 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 技術分享 >

阿裏P8架構師詳談 Java 内存模型

發表時(shí)間:2019-7-5

發布人(rén):融晨科技

浏覽次數:48

阿裏P8架構師詳談 Java 内存模型

Java 内存模型(JMM)描述了(le/liǎo) JVM 如何使用計算機的(de)内存(RAM)。JVM 是(shì)一個(gè)完整計算機的(de)模型,因此該模型包含了(le/liǎo)内存模型的(de)設計 —— JMM。

如果要(yào / yāo)正确地(dì / de)設計并發程序,了(le/liǎo)解 JMM 非常重要(yào / yāo)。JMM 描述了(le/liǎo)不(bù)同線程間如何以(yǐ)及何時(shí)可以(yǐ)看到(dào)其它線程寫入共享變量的(de)值,以(yǐ)及如何在(zài)必要(yào / yāo)時(shí)同步訪問共享變量。

最初的(de) JMM 設計不(bù)充分,因此 JMM 在(zài) Java 1.5 進行了(le/liǎo)修訂。此版本的(de) JMM 仍在(zài) Java 8 中使用。

Java Memory Model 内部實現

JVM 内部使用的(de) JMM 将内存劃分爲(wéi / wèi)線程棧和(hé / huò)堆。下圖從邏輯角度說(shuō)明了(le/liǎo) JMM:

阿裏P8架構師詳談 Java 内存模型

在(zài) JVM 中運行的(de)每個(gè)線程都有它自己的(de)線程棧,線程棧包含了(le/liǎo)線程調用了(le/liǎo)哪些方法以(yǐ)到(dào)達當前執行點的(de)信息,我們把它成爲(wéi / wèi)“調用棧(Call Stack)“。當線程執行其代碼時(shí),調用棧會發生變化。

線程棧還包含了(le/liǎo)正在(zài)執行的(de)每個(gè)方法的(de)所有的(de)局部變量(調用棧上(shàng)的(de)所有方法)。一個(gè)線程隻能訪問它自己的(de)線程棧,由線程創建的(de)局部變量對于(yú)創建它的(de)線程以(yǐ)外的(de)所有其他(tā)線程都是(shì)不(bù)可見的(de)。即使兩個(gè)線程正在(zài)執行完全相同的(de)代碼,兩個(gè)線程仍将在(zài)各自的(de)線程棧中創建自己的(de)局部變量。因此,每個(gè)線程都有自己的(de)每個(gè)局部變量的(de)版本。

基本類型(boolean,byte,short,char,int,long,float,double)完全存儲在(zài)線程棧裏,因此對其他(tā)線程是(shì)不(bù)可見的(de)。一個(gè)線程可以(yǐ)将一個(gè)基本類型的(de)變量副本傳遞給另一個(gè)線程,但它不(bù)能共享原始局部變量本身。

堆包含了(le/liǎo) Java 應用程序中創建的(de)所有對象,不(bù)管對象是(shì)哪個(gè)線程創建的(de),這(zhè)包括基本類型的(de)包裝版本(如 Byte,Integer,Long 等)。無論對象是(shì)創建成局部變量,還是(shì)作爲(wéi / wèi)另一個(gè)對象的(de)成員變量被創建,對象都存儲在(zài)堆中。

下圖說(shuō)明了(le/liǎo)調用棧和(hé / huò)局部變量存儲在(zài)線程棧中,而(ér)對象存儲在(zài)堆中。

阿裏P8架構師詳談 Java 内存模型

局部變量如果是(shì)基本類型,這(zhè)種情況下,變量完全存儲在(zài)線程棧上(shàng)。

局部變量如果是(shì)對象的(de)引用,這(zhè)種情況下,引用(局部變量)存儲在(zài)線程棧上(shàng),但對象本身存儲在(zài)堆上(shàng)。

對象中可能包含方法,而(ér)這(zhè)些方法中可能包含局部變量,這(zhè)種情況下,即使方法所屬的(de)對象存儲在(zài)堆上(shàng),但這(zhè)些局部變量卻是(shì)存儲在(zài)線程棧上(shàng)的(de)。

對象的(de)成員變量與對象本身一起存儲在(zài)堆上(shàng),當成員變量是(shì)基本類型以(yǐ)及是(shì)對象的(de)引用時(shí)都是(shì)如此。

靜态類型變量與類定義一起存儲在(zài)堆上(shàng)。

所有線程通過擁有對象引用去訪問堆中的(de)對象。當一個(gè)線程有權訪問一個(gè)對象時(shí),它也(yě)能訪問該對象的(de)成員變量。如果兩個(gè)線程同一時(shí)間調用同一對象的(de)一個(gè)方法,它們都可以(yǐ)訪問該對象的(de)成員變量,但每個(gè)線程都有自己局部變量的(de)副本。

這(zhè)是(shì)一個(gè)說(shuō)明上(shàng)述要(yào / yāo)點的(de)圖表:

阿裏P8架構師詳談 Java 内存模型

兩個(gè)線程各有一組局部變量,其中一個(gè)局部變量(Local Variable 2)指向堆中的(de)共享對象(Object 3)。兩個(gè)線程各自對同一各對象擁有不(bù)同的(de)引用,它們的(de)引用是(shì)局部變量,因此它們存儲在(zài)各自線程的(de)線程棧中。但是(shì),這(zhè)兩個(gè)不(bù)同引用指向堆中的(de)同一個(gè)對象。

請注意,共享對象(Object 3)将 Object 2 和(hé / huò) Object 4 作爲(wéi / wèi)成員變量引用(如從 Object 3 到(dào) Object 2 和(hé / huò) Object 4 的(de)箭頭所示),通過對象 3 中的(de)這(zhè)些成員變量引用,兩個(gè)線程可以(yǐ)訪問對象 2 和(hé / huò) 對象 4。

上(shàng)圖還顯示了(le/liǎo)一個(gè)局部變量指向堆中的(de)兩個(gè)不(bù)同對象。這(zhè)種情況下,引用指向兩個(gè)不(bù)同的(de)對象(Object 1 和(hé / huò) Object 5),而(ér)不(bù)是(shì)同一個(gè)對象。理論上(shàng),如果兩個(gè)線程都引用了(le/liǎo)兩個(gè)對象,那兩個(gè)線程都可以(yǐ)訪問對象 1 和(hé / huò) 對象 5。但在(zài)上(shàng)圖中,每個(gè)線程隻引用了(le/liǎo)兩個(gè)對象中的(de)一個(gè)。Java學習圈子(zǐ)

那麽,什麽樣的(de) Java 代碼可以(yǐ)導緻上(shàng)面的(de)内存圖?好吧,代碼就(jiù)如下面的(de)代碼一樣簡單:

阿裏P8架構師詳談 Java 内存模型

阿裏P8架構師詳談 Java 内存模型

如果兩個(gè)線程正在(zài)執行 run() 方法,則前面的(de)結果就(jiù)會出(chū)現。run() 方法會調用 methodOne(),而(ér) methodOne() 會調用 methodTwo()。

方法 methodOne() 中聲明了(le/liǎo)一個(gè)基本類型的(de)局部變量(localVariable1 類型 int)和(hé / huò)一個(gè)對象引用的(de)局部變量(localVariable2)。

每個(gè)執行 methodOne() 的(de)線程将在(zài)各自的(de)線程棧上(shàng)創建自己的(de) localVariable1 和(hé / huò) localVariable2 副本。localVariable 1 變量将完全分離,隻存在(zài)于(yú)每個(gè)線程的(de)線程棧中。一個(gè)線程無法看到(dào)另一個(gè)線程對其 localVariable 1 副本所做的(de)更改。

執行 methodOne() 的(de)每個(gè)線程還将創建它們自己的(de) localVariable2 副本。然而(ér),localVariable 2 的(de)兩個(gè)不(bù)同副本最終都指向堆上(shàng)的(de)同一個(gè)對象。代碼将 localVariable 2 設置爲(wéi / wèi)指向靜态變量引用的(de)對象。靜态變量隻有一個(gè)副本,這(zhè)個(gè)副本存儲在(zài)堆上(shàng)。因此,localVariable 2 的(de)兩個(gè)副本最終都指向靜态變量所指向的(de) MySharedObject 的(de)同一個(gè)實例。MySharedObject 實例也(yě)存儲在(zài)堆中,它對應于(yú)上(shàng)圖中的(de)對象 3。Java學習圈子(zǐ)???????

注意 MySharedObject 類也(yě)包含兩個(gè)成員變量。成員變量本身同對象一起存儲在(zài)堆中。這(zhè)兩個(gè)成員變量指向另外兩個(gè) Integer 對象,這(zhè)些 Integer 對象對應于(yú)上(shàng)圖中的(de)對象 2和(hé / huò)對象 4。

還要(yào / yāo)注意 methodTwo() 創建的(de)一個(gè)名爲(wéi / wèi) localVariable 1 的(de)本地(dì / de)變量。這(zhè)個(gè)局部變量是(shì)一個(gè)指向 Integer 對象的(de)對象引用。該方法将 localVariable 1 引用設置爲(wéi / wèi)指向一個(gè)新的(de) Integer 實例。localVariable 1 引用将存儲在(zài)每個(gè)執行 methodTwo() 的(de)線程的(de)一個(gè)副本中。實例化的(de)兩個(gè) Integer 對象存儲在(zài)堆上(shàng),但是(shì)由于(yú)方法每次執行都會創建一個(gè)新的(de) Integer 對象,因此執行該方法的(de)兩個(gè)線程将創建單獨的(de) Integer 實例。methodTwo() 中創建的(de) Integer 對象對應于(yú)上(shàng)圖中的(de)對象 1和(hé / huò)對象 5。還要(yào / yāo)注意類 MySharedObject 中的(de)兩個(gè)成員變量,它們的(de)類型是(shì) long,這(zhè)是(shì)一個(gè)基本類型。由于(yú)這(zhè)些變量是(shì)成員變量,所以(yǐ)它們仍然與對象一起存儲在(zài)堆中。隻有本地(dì / de)變量存儲在(zài)線程堆棧中。

硬件内存架構

現代硬件内存架構與 Java 内存模型略有不(bù)同。了(le/liǎo)解硬件内存架構也(yě)很重要(yào / yāo),以(yǐ)了(le/liǎo)解 Java 内存模型如何與其一起工作。本節介紹了(le/liǎo)常見的(de)硬件内存架構,後面的(de)部分将介紹 Java 内存模型如何與其配合使用。

這(zhè)是(shì)現代計算機硬件架構的(de)簡化圖:

阿裏P8架構師詳談 Java 内存模型

現代計算機通常有兩個(gè)或更多的(de) CPU,其中一些 CPU 也(yě)可能有多個(gè)内核。關鍵是(shì),在(zài)具有2個(gè)或更多 CPU 的(de)現代計算機上(shàng),可以(yǐ)同時(shí)運行多個(gè)線程。每個(gè) CPU 都能夠在(zài)任何給定時(shí)間運行一個(gè)線程。這(zhè)意味着如果您的(de) Java 應用程序是(shì)多線程的(de),那麽每個(gè) CPU 可能同時(shí)(并發地(dì / de))運行 Java 應用程序中的(de)一個(gè)線程。

每個(gè) CPU 包含一組寄存器,這(zhè)些寄存器本質上(shàng)是(shì)在(zài) CPU 内存中。CPU 在(zài)這(zhè)些寄存器上(shàng)執行操作的(de)速度要(yào / yāo)比在(zài)主内存中執行變量的(de)速度快得多。這(zhè)是(shì)因爲(wéi / wèi) CPU 訪問這(zhè)些寄存器的(de)速度要(yào / yāo)比訪問主内存快得多。

每個(gè) CPU 還可以(yǐ)有一個(gè) CPU 緩存内存層。事實上(shàng),大(dà)多數現代 CPU 都有某種大(dà)小的(de)緩存内存層。CPU 訪問緩存内存的(de)速度比主内存快得多,但通常沒有訪問内部寄存器的(de)速度快。因此,CPU 高速緩存存儲器介于(yú)内部寄存器和(hé / huò)主存儲器的(de)速度之(zhī)間。某些 CPU 可能有多個(gè)緩存層(L1 和(hé / huò) L2),但要(yào / yāo)了(le/liǎo)解 Java 内存模型如何與内存交互,這(zhè)一點并不(bù)重要(yào / yāo)。重要(yào / yāo)的(de)是(shì)要(yào / yāo)知道(dào) CPU 可以(yǐ)有某種緩存存儲層。

計算機還包含一個(gè)主内存區域(RAM)。所有 CPU 都可以(yǐ)訪問主存,主内存區域通常比 CPU 的(de)緩存内存大(dà)得多。

通常,當 CPU 需要(yào / yāo)訪問主内存時(shí),它會将部分主内存讀入 CPU 緩存。它甚至可以(yǐ)将緩存的(de)一部分讀入内部寄存器,然後對其執行操作。當 CPU 需要(yào / yāo)将結果寫回主内存時(shí),它會将值從内部寄存器刷新到(dào)緩存内存,并在(zài)某個(gè)時(shí)候将值刷新回主内存。

當CPU需要(yào / yāo)在(zài)高速緩存中存儲其他(tā)内容時(shí),通常會将存儲在(zài)高速緩存中的(de)值刷新回主内存。CPU 緩存可以(yǐ)一次将數據寫入一部分内存,并一次刷新一部分内存。它不(bù)必每次更新時(shí)都讀取/寫入完整的(de)緩存。通常,緩存是(shì)在(zài)稱爲(wéi / wèi)“緩存線(Cache Line)”的(de)較小内存塊中更新的(de)。可以(yǐ)将一條或多條高速緩存線讀入高速緩存内存,并将一條或多條高速緩存線再次刷新回主内存。

JMM 和(hé / huò)硬件内存結構之(zhī)間的(de)差别

如前所述,JMM 和(hé / huò)硬件内存結構是(shì)不(bù)同的(de)。硬件内存體系結構不(bù)區分線程棧和(hé / huò)堆。在(zài)硬件上(shàng),線程棧和(hé / huò)堆都位于(yú)主内存中。線程棧和(hé / huò)堆的(de)一部分有時(shí)可能存在(zài)于(yú) CPU 高速緩存和(hé / huò)内部 CPU 寄存器中。如下圖所示:

阿裏P8架構師詳談 Java 内存模型

當對象和(hé / huò)變量可以(yǐ)存儲在(zài)計算機的(de)不(bù)同内存區域時(shí),可能會出(chū)現某些問題。主要(yào / yāo)有兩個(gè)問題:

  • 線程更新(寫入)對共享變量的(de)可見性
  • 讀取、檢查和(hé / huò)寫入共享變量時(shí)的(de)競争條件

這(zhè)兩個(gè)問題将在(zài)下面幾節中進行解釋。Java學習圈子(zǐ)???????

共享對象的(de)可見性

如果兩個(gè)或多個(gè)線程共享一個(gè)對象,而(ér)沒有正确使用 volatile 聲明或同步,那麽一個(gè)線程對共享對象的(de)更新可能對其他(tā)線程不(bù)可見。

假設共享對象最初存儲在(zài)主内存中。在(zài) CPU 1 上(shàng)運行的(de)線程然後将共享對象讀入它的(de) CPU 緩存。在(zài)這(zhè)裏,它對共享對象進行更改。隻要(yào / yāo)沒有将 CPU 緩存刷新回主内存,在(zài)其他(tā) CPU 上(shàng)運行的(de)線程就(jiù)不(bù)會看到(dào)共享對象的(de)更改版本。這(zhè)樣,每個(gè)線程都可能最終擁有自己的(de)共享對象副本,每個(gè)副本位于(yú)不(bù)同的(de) CPU緩 存中。

下圖說(shuō)明了(le/liǎo)大(dà)緻的(de)情況。在(zài)左 CPU 上(shàng)運行的(de)一個(gè)線程将共享對象複制到(dào)其 CPU 緩存中,并将其 count 變量更改爲(wéi / wèi)2。此更改對運行在(zài)正确 CPU 上(shàng)的(de)其他(tā)線程不(bù)可見,因爲(wéi / wèi)尚未将更新刷新回主内存。

阿裏P8架構師詳談 Java 内存模型

要(yào / yāo)解決這(zhè)個(gè)問題,可以(yǐ)使用 Java 的(de) volatile 關鍵字。volatile 關鍵字可以(yǐ)确保直接從主内存讀取給定的(de)變量,并在(zài)更新時(shí)始終将其寫回主内存。

競态條件

如果兩個(gè)或多個(gè)線程共享一個(gè)對象,且多個(gè)線程更新該共享對象中的(de)變量,則可能出(chū)現競争條件。

假設線程 A 将共享對象的(de)變量計數讀入其 CPU 緩存。再想象一下,線程 B 執行相同的(de)操作,但是(shì)進入了(le/liǎo)不(bù)同的(de) CPU 緩存。現在(zài)線程 A 向 count 加一,線程 B 也(yě)這(zhè)樣做。現在(zài) var1 已經增加了(le/liǎo)兩次,每次在(zài)每個(gè) CPU 緩存中增加一次。

如果按順序執行這(zhè)些增量,變量計數将增加兩次,并将原始值 + 2 寫回主内存。

但是(shì),這(zhè)兩個(gè)增量是(shì)同時(shí)執行的(de),沒有适當的(de)同步。無論哪個(gè)線程 A 和(hé / huò)線程 B 将其更新版本的(de) count 寫回主内存,更新後的(de)值隻比原始值高1,盡管有兩個(gè)增量。

該圖說(shuō)明了(le/liǎo)上(shàng)述競态條件問題的(de)發生情況:

阿裏P8架構師詳談 Java 内存模型

要(yào / yāo)解決這(zhè)個(gè)問題,可以(yǐ)使用 Java synchronized 塊。同步塊保證在(zài)任何給定時(shí)間隻有一個(gè)線程可以(yǐ)進入代碼的(de)給定臨界段。Synchronized 塊還保證在(zài) Synchronized 塊中訪問的(de)所有變量都将從主内存中讀入,當線程退出(chū) Synchronized 塊時(shí),所有更新的(de)變量将再次刷新回主内存,而(ér)不(bù)管變量是(shì)否聲明爲(wéi / wèi) volatile。

粉絲福利:

爲(wéi / wèi)粉絲講解福利資源:特講解免費教程教你如何學習 ,源碼、分布式、微服務、性能優化、多線程并發,從0到(dào)1,帶你領略底層精髓。

如何學習:

阿裏P8架構師詳談 Java 内存模型

上(shàng)圖中的(de)資料都是(shì)我精心錄制視頻,感興趣的(de)可以(yǐ)加入我的(de)Java學習圈子(zǐ) 免費獲取。希望能夠在(zài)你接下來(lái)即将應對的(de)的(de)面試過程中能夠盡到(dào)一份綿薄之(zhī)力。

相關案例查看更多