溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java 中怎么引入內(nèi)存模型

發(fā)布時(shí)間:2021-06-30 17:50:32 來源:億速云 閱讀:128 作者:Leah 欄目:編程語言

今天就跟大家聊聊有關(guān) Java 中怎么引入內(nèi)存模型,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

JMM引入

從堆棧說起

JVM內(nèi)部使用的Java內(nèi)存模型在線程棧和堆之間劃分內(nèi)存。 此圖從邏輯角度說明了Java內(nèi)存模型: 

堆棧里面放了什么?

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

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

堆包含了在Java應(yīng)用程序中創(chuàng)建的所有對(duì)象,無論創(chuàng)建該對(duì)象的線程是什么。 這包括基本類型的包裝類(例如Byte,Integer,Long等)。 無論是創(chuàng)建對(duì)象并將其分配給局部變量,還是創(chuàng)建為另一個(gè)對(duì)象的成員變量,該對(duì)象仍然存儲(chǔ)在堆上。

Java 中怎么引入內(nèi)存模型

局部變量可以是基本類型,在這種情況下,它完全保留在線程堆棧上。

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

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

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

線程棧如何訪問堆上對(duì)象?

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

JVM 基礎(chǔ)系列 - Java 內(nèi)存模型引入

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

注意共享對(duì)象(對(duì)象3)如何將對(duì)象2和對(duì)象4作為成員變量引用(由對(duì)象3到對(duì)象2和對(duì)象4的箭頭所示)。 通過對(duì)象3中的這些成員變量引用,兩個(gè)線程可以訪問對(duì)象2和對(duì)象4.

該圖還顯示了一個(gè)局部變量,該變量指向堆上的兩個(gè)不同對(duì)象。 在這種情況下,引用指向兩個(gè)不同的對(duì)象(對(duì)象1和對(duì)象5),而不是同一個(gè)對(duì)象。 理論上,如果兩個(gè)線程都引用了兩個(gè)對(duì)象,則兩個(gè)線程都可以訪問對(duì)象1和對(duì)象5。 但是在上圖中,每個(gè)線程只引用了兩個(gè)對(duì)象中的一個(gè)。

線程棧訪問堆示例

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

Java 中怎么引入內(nèi)存模型

如果兩個(gè)線程正在執(zhí)行run()方法,則前面顯示的圖表將是結(jié)果。 run()方法調(diào)用methodOne(),methodOne()調(diào)用methodTwo()。

methodOne()聲明一個(gè)局部基本類型變量(類型為int的localVariable1)和一個(gè)局部變量,它是一個(gè)對(duì)象引用(localVariable2)。

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

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

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

另請(qǐng)注意methodTwo()如何創(chuàng)建名為localVariable1的局部變量。 此局部變量是對(duì)Integer對(duì)象的對(duì)象引用。 該方法將localVariable1引用設(shè)置為指向新的Integer實(shí)例。 localVariable1引用將存儲(chǔ)在執(zhí)行methodTwo()的每個(gè)線程的一個(gè)副本中。 實(shí)例化的兩個(gè)Integer對(duì)象將存儲(chǔ)在堆上,但由于該方法每次執(zhí)行該方法時(shí)都會(huì)創(chuàng)建一個(gè)新的Integer對(duì)象,因此執(zhí)行此方法的兩個(gè)線程將創(chuàng)建單獨(dú)的Integer實(shí)例。 在methodTwo()中創(chuàng)建的Integer對(duì)象對(duì)應(yīng)于上圖中的Object 1和Object 5。

另請(qǐng)注意類型為long的MySharedObject類中的兩個(gè)成員變量,它們是基本類型。 由于這些變量是成員變量,因此它們?nèi)耘c對(duì)象一起存儲(chǔ)在堆上。 只有局部變量存儲(chǔ)在線程堆棧中。

JMM與硬件內(nèi)存結(jié)構(gòu)關(guān)系

硬件內(nèi)存結(jié)構(gòu)簡(jiǎn)介

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

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

JVM 基礎(chǔ)系列 - Java 內(nèi)存模型引入

現(xiàn)代計(jì)算機(jī)通常有2個(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)用程序是多線程的,線程真的在可能同時(shí)運(yùn)行.

每個(gè)CPU基本上都包含一組在CPU內(nèi)存中的寄存器。 CPU可以在這些寄存器上執(zhí)行的操作比在主存儲(chǔ)器中對(duì)變量執(zhí)行的操作快得多。 這是因?yàn)镃PU可以比訪問主存儲(chǔ)器更快地訪問這些寄存器。

每個(gè)CPU還可以具有CPU高速緩存存儲(chǔ)器層。 事實(shí)上,大多數(shù)現(xiàn)代CPU都有一些大小的緩存存儲(chǔ)層。 CPU可以比主存儲(chǔ)器更快地訪問其高速緩存存儲(chǔ)器,但通常不會(huì)像訪問其內(nèi)部寄存器那樣快。 因此,CPU高速緩存存儲(chǔ)器介于內(nèi)部寄存器和主存儲(chǔ)器的速度之間。 某些CPU可能有多個(gè)緩存層(級(jí)別1和級(jí)別2),但要了解Java內(nèi)存模型如何與內(nèi)存交互,這一點(diǎn)并不重要。 重要的是要知道CPU可以有某種緩存存儲(chǔ)層。

計(jì)算機(jī)還包含主存儲(chǔ)區(qū)(RAM)。 所有CPU都可以訪問主內(nèi)存。 主存儲(chǔ)區(qū)通常比CPU的高速緩存存儲(chǔ)器大得多。同時(shí)訪問速度也就較慢.

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

JMM與硬件內(nèi)存連接 - 引入

如前所述,Java內(nèi)存模型和硬件內(nèi)存架構(gòu)是不同的。 硬件內(nèi)存架構(gòu)不區(qū)分線程堆棧和堆。 在硬件上,線程堆棧和堆都位于主存儲(chǔ)器中。 線程堆棧和堆的一部分有時(shí)可能存在于CPU高速緩存和內(nèi)部CPU寄存器中。 這在圖中說明:

Java 中怎么引入內(nèi)存模型

著作權(quán)歸https://pdai.tech所有。 鏈接:
https://pdai.tech/md/java/jvm/java-jvm-x-introduce.html

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

  • Visibility of thread updates (writes) to shared variables.

  • Race conditions when reading, checking and writing shared variables. 以下各節(jié)將解釋這兩個(gè)問題。

JMM與硬件內(nèi)存連接 - 對(duì)象共享后的可見性

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

想象一下,共享對(duì)象最初存儲(chǔ)在主存儲(chǔ)器中。 然后,在CPU上運(yùn)行的線程將共享對(duì)象讀入其CPU緩存中。 它在那里對(duì)共享對(duì)象進(jìn)行了更改。 只要CPU緩存尚未刷新回主內(nèi)存,共享對(duì)象的更改版本對(duì)于在其他CPU上運(yùn)行的線程是不可見的。 這樣,每個(gè)線程最終都可能擁有自己的共享對(duì)象副本,每個(gè)副本都位于不同的CPU緩存中。

下圖描繪了該情況。 在左CPU上運(yùn)行的一個(gè)線程將共享對(duì)象復(fù)制到其CPU緩存中,并將其count變量更改為2.對(duì)于在右邊的CPU上運(yùn)行的其他線程,此更改不可見,因?yàn)橛?jì)數(shù)更新尚未刷新回主內(nèi)存中。

JVM 基礎(chǔ)系列 - Java 內(nèi)存模型引入

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

JMM與硬件內(nèi)存連接 - 競(jìng)態(tài)條件

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,并且多個(gè)線程更新該共享對(duì)象中的變量,則可能會(huì)出現(xiàn)競(jìng)態(tài)。

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

如果這些增量是按先后順序執(zhí)行的,則變量計(jì)數(shù)將增加兩次并將原始值+ 2寫回主存儲(chǔ)器。

但是,兩個(gè)增量同時(shí)執(zhí)行而沒有適當(dāng)?shù)耐健?無論線程A和B中哪一個(gè)將其更新后的計(jì)數(shù)版本寫回主存儲(chǔ)器,更新的值將僅比原始值高1,盡管有兩個(gè)增量。

該圖說明了如上所述的競(jìng)爭(zhēng)條件問題的發(fā)生:

Java 中怎么引入內(nèi)存模型

看完上述內(nèi)容,你們對(duì) Java 中怎么引入內(nèi)存模型有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI