您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關 Java 中怎么引入內存模型,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
JMM引入
JVM內部使用的Java內存模型在線程棧和堆之間劃分內存。 此圖從邏輯角度說明了Java內存模型:
線程堆棧還包含正在執行的每個方法的所有局部變量(調用堆棧上的所有方法)。 線程只能訪問它自己的線程堆棧。 由線程創建的局部變量對于創建它的線程以外的所有其他線程是不可見的。 即使兩個線程正在執行完全相同的代碼,兩個線程仍將在每個自己的線程堆棧中創建該代碼的局部變量。 因此,每個線程都有自己的每個局部變量的版本。
基本類型的所有局部變量(boolean,byte,short,char,int,long,float,double)完全存儲在線程堆棧中,因此對其他線程不可見。 一個線程可以將一個基本類型變量的副本傳遞給另一個線程,但它不能共享原始局部變量本身。
堆包含了在Java應用程序中創建的所有對象,無論創建該對象的線程是什么。 這包括基本類型的包裝類(例如Byte,Integer,Long等)。 無論是創建對象并將其分配給局部變量,還是創建為另一個對象的成員變量,該對象仍然存儲在堆上。
局部變量可以是基本類型,在這種情況下,它完全保留在線程堆棧上。
局部變量也可以是對象的引用。 在這種情況下,引用(局部變量)存儲在線程堆棧中,但是對象本身存儲在堆(Heap)上。
對象的成員變量與對象本身一起存儲在堆上。 當成員變量是基本類型時,以及它是對象的引用時都是如此。
靜態類變量也與類定義一起存儲在堆上。
所有具有對象引用的線程都可以訪問堆上的對象。 當一個線程有權訪問一個對象時,它也可以訪問該對象的成員變量。 如果兩個線程同時在同一個對象上調用一個方法,它們都可以訪問該對象的成員變量,但每個線程都有自己的局部變量副本。
兩個線程有一組局部變量。 其中一個局部變量(局部變量2)指向堆上的共享對象(對象3)。 兩個線程各自對同一對象具有不同的引用。 它們的引用是局部變量,因此存儲在每個線程的線程堆棧中(在每個線程堆棧上)。 但是,這兩個不同的引用指向堆上的同一個對象。
注意共享對象(對象3)如何將對象2和對象4作為成員變量引用(由對象3到對象2和對象4的箭頭所示)。 通過對象3中的這些成員變量引用,兩個線程可以訪問對象2和對象4.
該圖還顯示了一個局部變量,該變量指向堆上的兩個不同對象。 在這種情況下,引用指向兩個不同的對象(對象1和對象5),而不是同一個對象。 理論上,如果兩個線程都引用了兩個對象,則兩個線程都可以訪問對象1和對象5。 但是在上圖中,每個線程只引用了兩個對象中的一個。
那么,什么樣的Java代碼可以導致上面的內存圖? 好吧,代碼就像下面的代碼一樣簡單:
如果兩個線程正在執行run()方法,則前面顯示的圖表將是結果。 run()方法調用methodOne(),methodOne()調用methodTwo()。
methodOne()聲明一個局部基本類型變量(類型為int的localVariable1)和一個局部變量,它是一個對象引用(localVariable2)。
執行methodOne()的每個線程將在各自的線程堆棧上創建自己的localVariable1和localVariable2副本。 localVariable1變量將完全相互分離,只存在于每個線程的線程堆棧中。 一個線程無法看到另一個線程對其localVariable1副本所做的更改。
執行methodOne()的每個線程也將創建自己的localVariable2副本。 但是,localVariable2的兩個不同副本最終都指向堆上的同一個對象。 代碼將localVariable2設置為指向靜態變量引用的對象。 靜態變量只有一個副本,此副本存儲在堆上。 因此,localVariable2的兩個副本最終都指向靜態變量指向的MySharedObject的同一個實例。 MySharedObject實例也存儲在堆上。 它對應于上圖中的對象3。
注意MySharedObject類還包含兩個成員變量。 成員變量本身與對象一起存儲在堆上。 兩個成員變量指向另外兩個Integer對象。 這些Integer對象對應于上圖中的Object 2和Object 4。
另請注意methodTwo()如何創建名為localVariable1的局部變量。 此局部變量是對Integer對象的對象引用。 該方法將localVariable1引用設置為指向新的Integer實例。 localVariable1引用將存儲在執行methodTwo()的每個線程的一個副本中。 實例化的兩個Integer對象將存儲在堆上,但由于該方法每次執行該方法時都會創建一個新的Integer對象,因此執行此方法的兩個線程將創建單獨的Integer實例。 在methodTwo()中創建的Integer對象對應于上圖中的Object 1和Object 5。
另請注意類型為long的MySharedObject類中的兩個成員變量,它們是基本類型。 由于這些變量是成員變量,因此它們仍與對象一起存儲在堆上。 只有局部變量存儲在線程堆棧中。
現代硬件內存架構與內部Java內存模型略有不同。 了解硬件內存架構也很重要,以了解Java內存模型如何與其一起工作。 本節介紹了常見的硬件內存架構,后面的部分將介紹Java內存模型如何與其配合使用。
這是現代計算機硬件架構的簡化圖:
現代計算機通常有2個或更多CPU。 其中一些CPU也可能有多個內核。 關鍵是,在具有2個或更多CPU的現代計算機上,可以同時運行多個線程。 每個CPU都能夠在任何給定時間運行一個線程。 這意味著如果您的Java應用程序是多線程的,線程真的在可能同時運行.
每個CPU基本上都包含一組在CPU內存中的寄存器。 CPU可以在這些寄存器上執行的操作比在主存儲器中對變量執行的操作快得多。 這是因為CPU可以比訪問主存儲器更快地訪問這些寄存器。
每個CPU還可以具有CPU高速緩存存儲器層。 事實上,大多數現代CPU都有一些大小的緩存存儲層。 CPU可以比主存儲器更快地訪問其高速緩存存儲器,但通常不會像訪問其內部寄存器那樣快。 因此,CPU高速緩存存儲器介于內部寄存器和主存儲器的速度之間。 某些CPU可能有多個緩存層(級別1和級別2),但要了解Java內存模型如何與內存交互,這一點并不重要。 重要的是要知道CPU可以有某種緩存存儲層。
計算機還包含主存儲區(RAM)。 所有CPU都可以訪問主內存。 主存儲區通常比CPU的高速緩存存儲器大得多。同時訪問速度也就較慢.
通常,當CPU需要訪問主存儲器時,它會將部分主存儲器讀入其CPU緩存。 它甚至可以將部分緩存讀入其內部寄存器,然后對其執行操作。 當CPU需要將結果寫回主存儲器時,它會將值從其內部寄存器刷新到高速緩沖存儲器,并在某些時候將值刷新回主存儲器。
如前所述,Java內存模型和硬件內存架構是不同的。 硬件內存架構不區分線程堆棧和堆。 在硬件上,線程堆棧和堆都位于主存儲器中。 線程堆棧和堆的一部分有時可能存在于CPU高速緩存和內部CPU寄存器中。 這在圖中說明:
著作權歸https://pdai.tech所有。 鏈接:
https://pdai.tech/md/java/jvm/java-jvm-x-introduce.html
當對象和變量可以存儲在計算機的各種不同存儲區域中時,可能會出現某些問題。 兩個主要問題是:
Visibility of thread updates (writes) to shared variables.
Race conditions when reading, checking and writing shared variables. 以下各節將解釋這兩個問題。
如果兩個或多個線程共享一個對象,而沒有正確使用volatile聲明或同步,則一個線程對共享對象的更新可能對其他線程不可見。
想象一下,共享對象最初存儲在主存儲器中。 然后,在CPU上運行的線程將共享對象讀入其CPU緩存中。 它在那里對共享對象進行了更改。 只要CPU緩存尚未刷新回主內存,共享對象的更改版本對于在其他CPU上運行的線程是不可見的。 這樣,每個線程最終都可能擁有自己的共享對象副本,每個副本都位于不同的CPU緩存中。
下圖描繪了該情況。 在左CPU上運行的一個線程將共享對象復制到其CPU緩存中,并將其count變量更改為2.對于在右邊的CPU上運行的其他線程,此更改不可見,因為計數更新尚未刷新回主內存中。
要解決此問題,您可以使用Java的volatile關鍵字。 volatile關鍵字可以確保直接從主內存讀取給定變量,并在更新時始終寫回主內存。
如果兩個或多個線程共享一個對象,并且多個線程更新該共享對象中的變量,則可能會出現競態。
想象一下,如果線程A將共享對象的變量計數讀入其CPU緩存中。 想象一下,線程B也做同樣的事情,但是進入不同的CPU緩存。 現在,線程A將一個添加到count,而線程B執行相同的操作。 現在var1已經增加了兩次,每個CPU緩存一次。
如果這些增量是按先后順序執行的,則變量計數將增加兩次并將原始值+ 2寫回主存儲器。
但是,兩個增量同時執行而沒有適當的同步。 無論線程A和B中哪一個將其更新后的計數版本寫回主存儲器,更新的值將僅比原始值高1,盡管有兩個增量。
該圖說明了如上所述的競爭條件問題的發生:
看完上述內容,你們對 Java 中怎么引入內存模型有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。