欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型 - 新聞資訊 - 云南小程序開(kāi)發(fā)|云南軟件開(kāi)發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開(kāi)發(fā)/軟件開(kāi)發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X(jué)表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁(yè) » 新聞資訊 » 技術(shù)分享 >

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

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

發(fā)布人:葵宇科技

瀏覽次數(shù):48

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

Java 內(nèi)存模型(JMM)描述了 JVM 如何使用計(jì)算機(jī)的內(nèi)存(RAM)。JVM 是一個(gè)完整計(jì)算機(jī)的模型,因此該模型包含了內(nèi)存模型的設(shè)計(jì) —— JMM。

如果要正確地設(shè)計(jì)并發(fā)程序,了解 JMM 非常重要。JMM 描述了不同線程間如何以及何時(shí)可以看到其它線程寫(xiě)入共享變量的值,以及如何在必要時(shí)同步訪問(wèn)共享變量。

最初的 JMM 設(shè)計(jì)不充分,因此 JMM 在 Java 1.5 進(jìn)行了修訂。此版本的 JMM 仍在 Java 8 中使用。

Java Memory Model 內(nèi)部實(shí)現(xiàn)

JVM 內(nèi)部使用的 JMM 將內(nèi)存劃分為線程棧和堆。下圖從邏輯角度說(shuō)明了 JMM:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

在 JVM 中運(yùn)行的每個(gè)線程都有它自己的線程棧,線程棧包含了線程調(diào)用了哪些方法以到達(dá)當(dāng)前執(zhí)行點(diǎn)的信息,我們把它成為“調(diào)用棧(Call Stack)“。當(dāng)線程執(zhí)行其代碼時(shí),調(diào)用棧會(huì)發(fā)生變化。

線程棧還包含了正在執(zhí)行的每個(gè)方法的所有的局部變量(調(diào)用棧上的所有方法)。一個(gè)線程只能訪問(wèn)它自己的線程棧,由線程創(chuàng)建的局部變量對(duì)于創(chuàng)建它的線程以外的所有其他線程都是不可見(jiàn)的。即使兩個(gè)線程正在執(zhí)行完全相同的代碼,兩個(gè)線程仍將在各自的線程棧中創(chuàng)建自己的局部變量。因此,每個(gè)線程都有自己的每個(gè)局部變量的版本。

基本類型(boolean,byte,short,char,int,long,float,double)完全存儲(chǔ)在線程棧里,因此對(duì)其他線程是不可見(jiàn)的。一個(gè)線程可以將一個(gè)基本類型的變量副本傳遞給另一個(gè)線程,但它不能共享原始局部變量本身。

堆包含了 Java 應(yīng)用程序中創(chuàng)建的所有對(duì)象,不管對(duì)象是哪個(gè)線程創(chuàng)建的,這包括基本類型的包裝版本(如 Byte,Integer,Long 等)。無(wú)論對(duì)象是創(chuàng)建成局部變量,還是作為另一個(gè)對(duì)象的成員變量被創(chuàng)建,對(duì)象都存儲(chǔ)在堆中。

下圖說(shuō)明了調(diào)用棧和局部變量存儲(chǔ)在線程棧中,而對(duì)象存儲(chǔ)在堆中。

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

局部變量如果是基本類型,這種情況下,變量完全存儲(chǔ)在線程棧上。

局部變量如果是對(duì)象的引用,這種情況下,引用(局部變量)存儲(chǔ)在線程棧上,但對(duì)象本身存儲(chǔ)在堆上。

對(duì)象中可能包含方法,而這些方法中可能包含局部變量,這種情況下,即使方法所屬的對(duì)象存儲(chǔ)在堆上,但這些局部變量卻是存儲(chǔ)在線程棧上的。

對(duì)象的成員變量與對(duì)象本身一起存儲(chǔ)在堆上,當(dāng)成員變量是基本類型以及是對(duì)象的引用時(shí)都是如此。

靜態(tài)類型變量與類定義一起存儲(chǔ)在堆上。

所有線程通過(guò)擁有對(duì)象引用去訪問(wèn)堆中的對(duì)象。當(dāng)一個(gè)線程有權(quán)訪問(wèn)一個(gè)對(duì)象時(shí),它也能訪問(wèn)該對(duì)象的成員變量。如果兩個(gè)線程同一時(shí)間調(diào)用同一對(duì)象的一個(gè)方法,它們都可以訪問(wèn)該對(duì)象的成員變量,但每個(gè)線程都有自己局部變量的副本。

這是一個(gè)說(shuō)明上述要點(diǎn)的圖表:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

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

請(qǐng)注意,共享對(duì)象(Object 3)將 Object 2 和 Object 4 作為成員變量引用(如從 Object 3 到 Object 2 和 Object 4 的箭頭所示),通過(guò)對(duì)象 3 中的這些成員變量引用,兩個(gè)線程可以訪問(wèn)對(duì)象 2 和 對(duì)象 4。

上圖還顯示了一個(gè)局部變量指向堆中的兩個(gè)不同對(duì)象。這種情況下,引用指向兩個(gè)不同的對(duì)象(Object 1 和 Object 5),而不是同一個(gè)對(duì)象。理論上,如果兩個(gè)線程都引用了兩個(gè)對(duì)象,那兩個(gè)線程都可以訪問(wèn)對(duì)象 1 和 對(duì)象 5。但在上圖中,每個(gè)線程只引用了兩個(gè)對(duì)象中的一個(gè)。Java學(xué)習(xí)圈子

那么,什么樣的 Java 代碼可以導(dǎo)致上面的內(nèi)存圖?好吧,代碼就如下面的代碼一樣簡(jiǎn)單:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

如果兩個(gè)線程正在執(zhí)行 run() 方法,則前面的結(jié)果就會(huì)出現(xiàn)。run() 方法會(huì)調(diào)用 methodOne(),而 methodOne() 會(huì)調(diào)用 methodTwo()。

方法 methodOne() 中聲明了一個(gè)基本類型的局部變量(localVariable1 類型 int)和一個(gè)對(duì)象引用的局部變量(localVariable2)。

每個(gè)執(zhí)行 methodOne() 的線程將在各自的線程棧上創(chuàng)建自己的 localVariable1 和 localVariable2 副本。localVariable 1 變量將完全分離,只存在于每個(gè)線程的線程棧中。一個(gè)線程無(wú)法看到另一個(gè)線程對(duì)其 localVariable 1 副本所做的更改。

執(zhí)行 methodOne() 的每個(gè)線程還將創(chuàng)建它們自己的 localVariable2 副本。然而,localVariable 2 的兩個(gè)不同副本最終都指向堆上的同一個(gè)對(duì)象。代碼將 localVariable 2 設(shè)置為指向靜態(tài)變量引用的對(duì)象。靜態(tài)變量只有一個(gè)副本,這個(gè)副本存儲(chǔ)在堆上。因此,localVariable 2 的兩個(gè)副本最終都指向靜態(tài)變量所指向的 MySharedObject 的同一個(gè)實(shí)例。MySharedObject 實(shí)例也存儲(chǔ)在堆中,它對(duì)應(yīng)于上圖中的對(duì)象 3。Java學(xué)習(xí)圈子???????

注意 MySharedObject 類也包含兩個(gè)成員變量。成員變量本身同對(duì)象一起存儲(chǔ)在堆中。這兩個(gè)成員變量指向另外兩個(gè) Integer 對(duì)象,這些 Integer 對(duì)象對(duì)應(yīng)于上圖中的對(duì)象 2和對(duì)象 4。

還要注意 methodTwo() 創(chuàng)建的一個(gè)名為 localVariable 1 的本地變量。這個(gè)局部變量是一個(gè)指向 Integer 對(duì)象的對(duì)象引用。該方法將 localVariable 1 引用設(shè)置為指向一個(gè)新的 Integer 實(shí)例。localVariable 1 引用將存儲(chǔ)在每個(gè)執(zhí)行 methodTwo() 的線程的一個(gè)副本中。實(shí)例化的兩個(gè) Integer 對(duì)象存儲(chǔ)在堆上,但是由于方法每次執(zhí)行都會(huì)創(chuàng)建一個(gè)新的 Integer 對(duì)象,因此執(zhí)行該方法的兩個(gè)線程將創(chuàng)建單獨(dú)的 Integer 實(shí)例。methodTwo() 中創(chuàng)建的 Integer 對(duì)象對(duì)應(yīng)于上圖中的對(duì)象 1和對(duì)象 5。還要注意類 MySharedObject 中的兩個(gè)成員變量,它們的類型是 long,這是一個(gè)基本類型。由于這些變量是成員變量,所以它們?nèi)匀慌c對(duì)象一起存儲(chǔ)在堆中。只有本地變量存儲(chǔ)在線程堆棧中。

硬件內(nèi)存架構(gòu)

現(xiàn)代硬件內(nèi)存架構(gòu)與 Java 內(nèi)存模型略有不同。了解硬件內(nèi)存架構(gòu)也很重要,以了解 Java 內(nèi)存模型如何與其一起工作。本節(jié)介紹了常見(jiàn)的硬件內(nèi)存架構(gòu),后面的部分將介紹 Java 內(nèi)存模型如何與其配合使用。

這是現(xiàn)代計(jì)算機(jī)硬件架構(gòu)的簡(jiǎn)化圖:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

現(xiàn)代計(jì)算機(jī)通常有兩個(gè)或更多的 CPU,其中一些 CPU 也可能有多個(gè)內(nèi)核。關(guān)鍵是,在具有2個(gè)或更多 CPU 的現(xiàn)代計(jì)算機(jī)上,可以同時(shí)運(yùn)行多個(gè)線程。每個(gè) CPU 都能夠在任何給定時(shí)間運(yùn)行一個(gè)線程。這意味著如果您的 Java 應(yīng)用程序是多線程的,那么每個(gè) CPU 可能同時(shí)(并發(fā)地)運(yùn)行 Java 應(yīng)用程序中的一個(gè)線程。

每個(gè) CPU 包含一組寄存器,這些寄存器本質(zhì)上是在 CPU 內(nèi)存中。CPU 在這些寄存器上執(zhí)行操作的速度要比在主內(nèi)存中執(zhí)行變量的速度快得多。這是因?yàn)?CPU 訪問(wèn)這些寄存器的速度要比訪問(wèn)主內(nèi)存快得多。

每個(gè) CPU 還可以有一個(gè) CPU 緩存內(nèi)存層。事實(shí)上,大多數(shù)現(xiàn)代 CPU 都有某種大小的緩存內(nèi)存層。CPU 訪問(wèn)緩存內(nèi)存的速度比主內(nèi)存快得多,但通常沒(méi)有訪問(wèn)內(nèi)部寄存器的速度快。因此,CPU 高速緩存存儲(chǔ)器介于內(nèi)部寄存器和主存儲(chǔ)器的速度之間。某些 CPU 可能有多個(gè)緩存層(L1 和 L2),但要了解 Java 內(nèi)存模型如何與內(nèi)存交互,這一點(diǎn)并不重要。重要的是要知道 CPU 可以有某種緩存存儲(chǔ)層。

計(jì)算機(jī)還包含一個(gè)主內(nèi)存區(qū)域(RAM)。所有 CPU 都可以訪問(wèn)主存,主內(nèi)存區(qū)域通常比 CPU 的緩存內(nèi)存大得多。

通常,當(dāng) CPU 需要訪問(wèn)主內(nèi)存時(shí),它會(huì)將部分主內(nèi)存讀入 CPU 緩存。它甚至可以將緩存的一部分讀入內(nèi)部寄存器,然后對(duì)其執(zhí)行操作。當(dāng) CPU 需要將結(jié)果寫(xiě)回主內(nèi)存時(shí),它會(huì)將值從內(nèi)部寄存器刷新到緩存內(nèi)存,并在某個(gè)時(shí)候?qū)⒅邓⑿禄刂鲀?nèi)存。

當(dāng)CPU需要在高速緩存中存儲(chǔ)其他內(nèi)容時(shí),通常會(huì)將存儲(chǔ)在高速緩存中的值刷新回主內(nèi)存。CPU 緩存可以一次將數(shù)據(jù)寫(xiě)入一部分內(nèi)存,并一次刷新一部分內(nèi)存。它不必每次更新時(shí)都讀取/寫(xiě)入完整的緩存。通常,緩存是在稱為“緩存線(Cache Line)”的較小內(nèi)存塊中更新的。可以將一條或多條高速緩存線讀入高速緩存內(nèi)存,并將一條或多條高速緩存線再次刷新回主內(nèi)存。

JMM 和硬件內(nèi)存結(jié)構(gòu)之間的差別

如前所述,JMM 和硬件內(nèi)存結(jié)構(gòu)是不同的。硬件內(nèi)存體系結(jié)構(gòu)不區(qū)分線程棧和堆。在硬件上,線程棧和堆都位于主內(nèi)存中。線程棧和堆的一部分有時(shí)可能存在于 CPU 高速緩存和內(nèi)部 CPU 寄存器中。如下圖所示:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

當(dāng)對(duì)象和變量可以存儲(chǔ)在計(jì)算機(jī)的不同內(nèi)存區(qū)域時(shí),可能會(huì)出現(xiàn)某些問(wèn)題。主要有兩個(gè)問(wèn)題:

  • 線程更新(寫(xiě)入)對(duì)共享變量的可見(jiàn)性
  • 讀取、檢查和寫(xiě)入共享變量時(shí)的競(jìng)爭(zhēng)條件

這兩個(gè)問(wèn)題將在下面幾節(jié)中進(jìn)行解釋。Java學(xué)習(xí)圈子???????

共享對(duì)象的可見(jiàn)性

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,而沒(méi)有正確使用 volatile 聲明或同步,那么一個(gè)線程對(duì)共享對(duì)象的更新可能對(duì)其他線程不可見(jiàn)。

假設(shè)共享對(duì)象最初存儲(chǔ)在主內(nèi)存中。在 CPU 1 上運(yùn)行的線程然后將共享對(duì)象讀入它的 CPU 緩存。在這里,它對(duì)共享對(duì)象進(jìn)行更改。只要沒(méi)有將 CPU 緩存刷新回主內(nèi)存,在其他 CPU 上運(yùn)行的線程就不會(huì)看到共享對(duì)象的更改版本。這樣,每個(gè)線程都可能最終擁有自己的共享對(duì)象副本,每個(gè)副本位于不同的 CPU緩 存中。

下圖說(shuō)明了大致的情況。在左 CPU 上運(yùn)行的一個(gè)線程將共享對(duì)象復(fù)制到其 CPU 緩存中,并將其 count 變量更改為2。此更改對(duì)運(yùn)行在正確 CPU 上的其他線程不可見(jiàn),因?yàn)樯形磳⒏滤⑿禄刂鲀?nèi)存。

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

要解決這個(gè)問(wèn)題,可以使用 Java 的 volatile 關(guān)鍵字。volatile 關(guān)鍵字可以確保直接從主內(nèi)存讀取給定的變量,并在更新時(shí)始終將其寫(xiě)回主內(nèi)存。

競(jìng)態(tài)條件

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,且多個(gè)線程更新該共享對(duì)象中的變量,則可能出現(xiàn)競(jìng)爭(zhēng)條件。

假設(shè)線程 A 將共享對(duì)象的變量計(jì)數(shù)讀入其 CPU 緩存。再想象一下,線程 B 執(zhí)行相同的操作,但是進(jìn)入了不同的 CPU 緩存?,F(xiàn)在線程 A 向 count 加一,線程 B 也這樣做?,F(xiàn)在 var1 已經(jīng)增加了兩次,每次在每個(gè) CPU 緩存中增加一次。

如果按順序執(zhí)行這些增量,變量計(jì)數(shù)將增加兩次,并將原始值 + 2 寫(xiě)回主內(nèi)存。

但是,這兩個(gè)增量是同時(shí)執(zhí)行的,沒(méi)有適當(dāng)?shù)耐?。無(wú)論哪個(gè)線程 A 和線程 B 將其更新版本的 count 寫(xiě)回主內(nèi)存,更新后的值只比原始值高1,盡管有兩個(gè)增量。

該圖說(shuō)明了上述競(jìng)態(tài)條件問(wèn)題的發(fā)生情況:

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

要解決這個(gè)問(wèn)題,可以使用 Java synchronized 塊。同步塊保證在任何給定時(shí)間只有一個(gè)線程可以進(jìn)入代碼的給定臨界段。Synchronized 塊還保證在 Synchronized 塊中訪問(wèn)的所有變量都將從主內(nèi)存中讀入,當(dāng)線程退出 Synchronized 塊時(shí),所有更新的變量將再次刷新回主內(nèi)存,而不管變量是否聲明為 volatile。

粉絲福利:

為粉絲講解福利資源:特講解免費(fèi)教程教你如何學(xué)習(xí) ,源碼、分布式、微服務(wù)、性能優(yōu)化、多線程并發(fā),從0到1,帶你領(lǐng)略底層精髓。

如何學(xué)習(xí):

阿里P8架構(gòu)師詳談 Java 內(nèi)存模型

上圖中的資料都是我精心錄制視頻,感興趣的可以加入我的Java學(xué)習(xí)圈子 免費(fèi)獲取。希望能夠在你接下來(lái)即將應(yīng)對(duì)的的面試過(guò)程中能夠盡到一份綿薄之力。

相關(guān)案例查看更多