Java內(nèi)存模型原理是什么-創(chuàng)新互聯(lián)

這篇文章主要介紹“Java內(nèi)存模型原理是什么”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Java內(nèi)存模型原理是什么”文章能幫助大家解決問題。

站在用戶的角度思考問題,與客戶深入溝通,找到偃師網(wǎng)站設(shè)計與偃師網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋偃師地區(qū)。

內(nèi)部原理

JVM 中試圖定義一種 JMM 來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓 Java 程序在各種平臺下都能達到一致的內(nèi)存訪問效果。

JMM 的主要目標是定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。此處的變量與 Java 編程中的變量有所區(qū)別,它包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但不包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享,自然就不會存在競爭問題。為了獲得較好的執(zhí)行效能,Java 內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主存進行交互,也沒有限制即使編譯器進行調(diào)整代碼執(zhí)行順序這類優(yōu)化措施。

JMM 是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這 3 個特征來建立的。

JMM 是通過各種操作來定義的,包括對變量的讀寫操作,監(jiān)視器的加鎖和釋放操作,以及線程的啟動和合并操作。

內(nèi)存模型結(jié)構(gòu)

Java 內(nèi)存模型把 Java 虛擬機內(nèi)部劃分為線程棧和堆。

線程棧

每一個運行在 Java 虛擬機里的線程都擁有自己的線程棧。這個線程棧包含了這個線程調(diào)用的方法當前執(zhí)行點相關(guān)的信息。一個線程僅能訪問自己的線程棧。一個線程創(chuàng)建的本地變量對其它線程不可見,僅自己可見。即使兩個線程執(zhí)行同樣的代碼,這兩個線程任然在在自己的線程棧中的代碼來創(chuàng)建本地變量。因此,每個線程擁有每個本地變量的獨有版本。

所有原始類型的本地變量都存放在線程棧上,因此對其它線程不可見。一個線程可能向另一個線程傳遞一個原始類型變量的拷貝,但是它不能共享這個原始類型變量自身。

堆上包含在 Java 程序中創(chuàng)建的所有對象,無論是哪一個對象創(chuàng)建的。這包括原始類型的對象版本。如果一個對象被創(chuàng)建然后賦值給一個局部變量,或者用來作為另一個對象的成員變量,這個對象任然是存放在堆上。

  • 一個本地變量可能是原始類型,在這種情況下,它總是在線程棧上。

  • 一個本地變量也可能是指向一個對象的一個引用。在這種情況下,引用(這個本地變量)存放在線程棧上,但是對象本身存放在堆上。

  • 一個對象可能包含方法,這些方法可能包含本地變量。這些本地變量任然存放在線程棧上,即使這些方法所屬的對象存放在堆上。

  • 一個對象的成員變量可能隨著這個對象自身存放在堆上。不管這個成員變量是原始類型還是引用類型。

  • 靜態(tài)成員變量跟隨著類定義一起也存放在堆上。

  • 存放在堆上的對象可以被所有持有對這個對象引用的線程訪問。當一個線程可以訪問一個對象時,它也可以訪問這個對象的成員變量。如果兩個線程同時調(diào)用同一個對象上的同一個方法,它們將會都訪問這個對象的成員變量,但是每一個線程都擁有這個本地變量的私有拷貝。

Java內(nèi)存模型原理是什么

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

現(xiàn)代硬件內(nèi)存模型與 Java 內(nèi)存模型有一些不同。理解內(nèi)存模型架構(gòu)以及 Java 內(nèi)存模型如何與它協(xié)同工作也是非常重要的。這部分描述了通用的硬件內(nèi)存架構(gòu),下面的部分將會描述 Java 內(nèi)存是如何與它“聯(lián)手”工作的。

Java內(nèi)存模型原理是什么

一個現(xiàn)代計算機通常由兩個或者多個 CPU。其中一些 CPU 還有多核。從這一點可以看出,在一個有兩個或者多個 CPU 的現(xiàn)代計算機上同時運行多個線程是可能的。每個 CPU 在某一時刻運行一個線程是沒有問題的。這意味著,如果你的 Java 程序是多線程的,在你的 Java 程序中每個 CPU 上一個線程可能同時(并發(fā))執(zhí)行。

每個 CPU 都包含一系列的寄存器,它們是 CPU 內(nèi)內(nèi)存的基礎(chǔ)。CPU 在寄存器上執(zhí)行操作的速度遠大于在主存上執(zhí)行的速度。這是因為 CPU 訪問寄存器的速度遠大于主存。

每個 CPU 可能還有一個 CPU 緩存層。實際上,絕大多數(shù)的現(xiàn)代 CPU 都有一定大小的緩存層。CPU 訪問緩存層的速度快于訪問主存的速度,但通常比訪問內(nèi)部寄存器的速度還要慢一點。一些 CPU 還有多層緩存,但這些對理解 Java 內(nèi)存模型如何和內(nèi)存交互不是那么重要。只要知道 CPU 中可以有一個緩存層就可以了。

一個計算機還包含一個主存。所有的 CPU 都可以訪問主存。主存通常比 CPU 中的緩存大得多。

通常情況下,當一個 CPU 需要讀取主存時,它會將主存的部分讀到 CPU 緩存中。它甚至可能將緩存中的部分內(nèi)容讀到它的內(nèi)部寄存器中,然后在寄存器中執(zhí)行操作。當 CPU 需要將結(jié)果寫回到主存中去時,它會將內(nèi)部寄存器的值刷新到緩存中,然后在某個時間點將值刷新回主存。

當 CPU 需要在緩存層存放一些東西的時候,存放在緩存中的內(nèi)容通常會被刷新回主存。CPU 緩存可以在某一時刻將數(shù)據(jù)局部寫到它的內(nèi)存中,和在某一時刻局部刷新它的內(nèi)存。它不會再某一時刻讀/寫整個緩存。通常,在一個被稱作“cache lines”的更小的內(nèi)存塊中緩存被更新。一個或者多個緩存行可能被讀到緩存,一個或者多個緩存行可能再被刷新回主存。

JMM 和硬件內(nèi)存架構(gòu)之間的橋接

上面已經(jīng)提到,Java 內(nèi)存模型與硬件內(nèi)存架構(gòu)之間存在差異。硬件內(nèi)存架構(gòu)沒有區(qū)分線程棧和堆。對于硬件,所有的線程棧和堆都分布在主內(nèi)中。部分線程棧和堆可能有時候會出現(xiàn)在 CPU 緩存中和 CPU 內(nèi)部的寄存器中。如下圖所示:

當對象和變量被存放在計算機中各種不同的內(nèi)存區(qū)域中時,就可能會出現(xiàn)一些具體的問題。主要包括如下兩個方面:

  • 線程對共享變量修改的可見性

  • 當讀,寫和檢查共享變量時出現(xiàn) race conditions

Java內(nèi)存模型原理是什么

共享對象可見性

如果兩個或者更多的線程在沒有正確的使用 volatile 聲明或者同步的情況下共享一個對象,一個線程更新這個共享對象可能對其它線程來說是不接見的。

想象一下,共享對象被初始化在主存中。跑在 CPU 上的一個線程將這個共享對象讀到 CPU 緩存中。然后修改了這個對象。只要 CPU 緩存沒有被刷新會主存,對象修改后的版本對跑在其它 CPU 上的線程都是不可見的。這種方式可能導(dǎo)致每個線程擁有這個共享對象的私有拷貝,每個拷貝停留在不同的 CPU 緩存中。

上圖示意了這種情形。跑在左邊 CPU 的線程拷貝這個共享對象到它的 CPU 緩存中,然后將 count 變量的值修改為 2。這個修改對跑在右邊 CPU 上的其它線程是不可見的,因為修改后的 count 的值還沒有被刷新回主存中去。

解決這個問題你可以使用 Java 中的 volatile 關(guān)鍵字。volatile 關(guān)鍵字可以保證直接從主存中讀取一個變量,如果這個變量被修改后,總是會被寫回到主存中去。

競態(tài)條件

如果兩個或者更多的線程共享一個對象,多個線程在這個共享對象上更新變量,就有可能發(fā)生 race conditions。

想象一下,如果線程 A 讀一個共享對象的變量 count 到它的 CPU 緩存中。再想象一下,線程 B 也做了同樣的事情,但是往一個不同的 CPU 緩存中。現(xiàn)在線程 A 將 count 加 1,線程 B 也做了同樣的事情?,F(xiàn)在 count 已經(jīng)被增在了兩個,每個 CPU 緩存中一次。

如果這些增加操作被順序的執(zhí)行,變量 count 應(yīng)該被增加兩次,然后原值+2 被寫回到主存中去。

然而,兩次增加都是在沒有適當?shù)耐较虏l(fā)執(zhí)行的。無論是線程 A 還是線程 B 將 count 修改后的版本寫回到主存中取,修改后的值僅會被原值大 1,盡管增加了兩次。

解決這個問題可以使用 Java 同步塊。一個同步塊可以保證在同一時刻僅有一個線程可以進入代碼的臨界區(qū)。同步塊還可以保證代碼塊中所有被訪問的變量將會從主存中讀入,當線程退出同步代碼塊時,所有被更新的變量都會被刷新回主存中去,不管這個變量是否被聲明為 volatile。

Happens-Before

JMM 為程序中所有的操作定義了一個偏序關(guān)系,稱之為 Happens-Before。

  • 程序順序規(guī)則:如果程序中操作 A 在操作 B 之前,那么在線程中操作 A 將在操作 B 之前執(zhí)行。

  • 監(jiān)視器鎖規(guī)則:在監(jiān)視器鎖上的解鎖操作必須在同一個監(jiān)視器鎖上的加鎖操作之前執(zhí)行。

  • volatile 變量規(guī)則:對 volatile 變量的寫入操作必須在對該變量的讀操作之前執(zhí)行。

  • 線程啟動規(guī)則:在線程上對 Thread.start 的調(diào)用必須在該線程中執(zhí)行任何操作之前執(zhí)行。

  • 線程結(jié)束規(guī)則:線程中的任何操作都必須在其他線程檢測到該線程已經(jīng)結(jié)束之前執(zhí)行,或者從 Thread.join 中成功返回,或者在調(diào)用 Thread.isAlive 時返回 false。

  • 中斷規(guī)則:當一個線程在另一個線程上調(diào)用 interrupt 時,必須在被中斷線程檢測到 interrupt 調(diào)用之前執(zhí)行(通過拋出 InterruptException,或者調(diào)用 isInterrupted 和 interrupted)。

  • 終結(jié)器規(guī)則:對象的構(gòu)造函數(shù)必須在啟動該對象的終結(jié)器之前執(zhí)行完成。

  • 傳遞性:如果操作 A 在操作 B 之前執(zhí)行,并且操作 B 在操作 C 之前執(zhí)行,那么操作 A 必須在操作 C 之前執(zhí)行。

關(guān)于“Java內(nèi)存模型原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

分享名稱:Java內(nèi)存模型原理是什么-創(chuàng)新互聯(lián)
網(wǎng)站URL:http://www.muchs.cn/article10/dpjhgo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、外貿(mào)網(wǎng)站建設(shè)品牌網(wǎng)站設(shè)計、網(wǎng)站排名微信公眾號、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)