溫馨提示×

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

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

jvm中java內(nèi)存模型的示例分析

發(fā)布時(shí)間:2022-01-14 10:59:13 來(lái)源:億速云 閱讀:126 作者:小新 欄目:大數(shù)據(jù)

這篇文章主要介紹了jvm中java內(nèi)存模型的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

一、java內(nèi)存模型和java內(nèi)存結(jié)構(gòu)有什么區(qū)別

 

1、java內(nèi)存結(jié)構(gòu)

記得是在好幾年前研究Android的時(shí)候,看的java內(nèi)存模型,時(shí)常和java內(nèi)存結(jié)構(gòu)分不清,因此,這一小節(jié)是針對(duì)小白或者是對(duì)其概念還不理解的人。

我們都知道,我們的java代碼其實(shí)是不能直接運(yùn)行的,他要經(jīng)過(guò)一系列的步驟。看下圖:

jvm中java內(nèi)存模型的示例分析

我們的java文件,首先要經(jīng)過(guò)編程成為class文件,然后通過(guò)類裝載器加載到j(luò)vm中去執(zhí)行。這個(gè)jvm(紅色虛線框起來(lái)的這部分)就是java運(yùn)行時(shí)數(shù)據(jù)區(qū),意思就是java代碼在運(yùn)行的時(shí)候,這些數(shù)據(jù)要存放在不同的內(nèi)存空間里面。jvm就是指代這個(gè)的。當(dāng)然了上面的運(yùn)行時(shí)數(shù)據(jù)區(qū)jvm是jdk1.7版本的。也就是說(shuō)不同的jdk版本,這個(gè)jvm長(zhǎng)得是不一樣的。我們可以把java7的內(nèi)存結(jié)構(gòu)拿出來(lái):

jvm中java內(nèi)存模型的示例分析

我們可以看到一共劃分了5個(gè)部分,其中java堆區(qū)和方法區(qū)還是所有線程共享的一片區(qū)域。為什么要所有線程共享呢?因?yàn)榧僭O(shè)一個(gè)數(shù)據(jù),每個(gè)線程都保留一份,那其中有一個(gè)線程調(diào)皮,把這個(gè)數(shù)據(jù)更改了。其他的線程發(fā)現(xiàn)自己的數(shù)據(jù)沒(méi)有變,這就出現(xiàn)了問(wèn)題了。于是設(shè)計(jì)成了所有線程共享,java內(nèi)存模型出來(lái)了。

 

2、java內(nèi)存模型

java內(nèi)存模型也叫做JMM,但是這個(gè)模型可不是像java內(nèi)存結(jié)構(gòu)一樣,是真實(shí)存在的。java內(nèi)存模型是一個(gè)抽象出來(lái)的概念。意思是把一部分內(nèi)存區(qū)域設(shè)計(jì)成所有線程共享的,一個(gè)線程對(duì)數(shù)據(jù)更改,其他線程就能立刻知道。這種設(shè)計(jì)的方法叫做內(nèi)存模型。我們可以提前看一下:java內(nèi)存模型長(zhǎng)什么樣。

jvm中java內(nèi)存模型的示例分析

這就是java內(nèi)存模型,也就是多個(gè)線程共享同一份數(shù)據(jù)。現(xiàn)在不知道你理解java內(nèi)存模型和java內(nèi)存結(jié)構(gòu)的區(qū)別了沒(méi)有,我們可以這樣來(lái)總結(jié)一下:

(1)java內(nèi)存結(jié)構(gòu)是解決java中的數(shù)據(jù)如何存放的問(wèn)題。

(2)java內(nèi)存模型是解決java中多個(gè)線程共享數(shù)據(jù)的問(wèn)題。

OK,到了這基本上就算是把兩者的區(qū)別介紹完了。下面就來(lái)看看為什么要有內(nèi)存模型吧

 

二、為什么要有內(nèi)存模型

深入理解java虛擬機(jī)是從硬件的發(fā)展來(lái)分析的。因此,我也將從這個(gè)角度來(lái)分析。

 

階段一

在計(jì)算機(jī)發(fā)展的第一個(gè)階段,程序是在CPU中運(yùn)行,數(shù)據(jù)在主存中保存。隨著技術(shù)的發(fā)展,CPU的速度越來(lái)越多高,但是主存的速度卻沒(méi)有提高太多。就好比是下面這種情況:

jvm中java內(nèi)存模型的示例分析

 

階段二

為了解決上面的問(wèn)題,于是乎出現(xiàn)了緩存,里面存放了一些CPU經(jīng)常使用的主存數(shù)據(jù),緩存的速度和CPU差不多,當(dāng)CPU查找數(shù)據(jù)的時(shí)候,首先從緩存中查找,沒(méi)有的話再?gòu)闹鞔嬷胁檎?。寫?shù)據(jù)的時(shí)候,先寫緩存的數(shù)據(jù),然后再更新到主存中。這樣一種機(jī)制使得速度提高很多。

 

階段三

技術(shù)繼續(xù)發(fā)展,在上面緩存的基礎(chǔ)上出現(xiàn)了一級(jí)二級(jí)三級(jí)緩存,查找也是逐層的,第一級(jí)緩存沒(méi)有就到第二層,就這樣以此類推。這時(shí)候CPU也得到了快讀發(fā)展,由之前的一個(gè)核變成了多核CPU(一個(gè)CPU變成了多部分)。

jvm中java內(nèi)存模型的示例分析

這時(shí)候呢,之前只能同時(shí)跑一個(gè)線程,現(xiàn)在就能跑很多個(gè)線程了。而且從上面我們可以看到,每一個(gè)核都有相應(yīng)的緩存區(qū),但是主內(nèi)存還是哪一個(gè)。既然能同時(shí)跑多個(gè)線程,那速度肯定杠杠滴就上去了吧,不跑不知道,一跑嚇一跳,立馬出現(xiàn)了很多個(gè)問(wèn)題。

問(wèn)題一:緩存一致性問(wèn)題

也就是說(shuō),每一個(gè)核都有自己的緩存區(qū),但是這些緩存區(qū)保存的數(shù)據(jù)卻不一樣。一張圖就明白了:

jvm中java內(nèi)存模型的示例分析

問(wèn)題二:處理器優(yōu)化和指令重排

這問(wèn)題的意思是,既然CPU有這么多內(nèi)核,肯定是想讓資源得到充分利用,于是把我們寫的程序拆分,對(duì)一些代碼進(jìn)行亂序處理,這就是處理器優(yōu)化。而且,java虛擬機(jī)一看CPU的這個(gè)操作真的強(qiáng),于是就模仿了一下,創(chuàng)建了即時(shí)編譯器(JIT),這個(gè)編譯器也會(huì)做指令重排的操作。很明顯,我們的代碼順序被打亂,指令被重排,就可能不會(huì)按照我們的意愿去執(zhí)行了。

上面出現(xiàn)的這些問(wèn)題,好像都是從硬件的角度來(lái)分析的,《深入理解java虛擬機(jī)》一書于是引出了軟件的問(wèn)題,也就是說(shuō),上面的這些問(wèn)題如果轉(zhuǎn)化到軟件層次會(huì)帶來(lái)什么問(wèn)題呢?

問(wèn)題三:軟件問(wèn)題

(1)原子性問(wèn)題

首先緩存一致性問(wèn)題在程序中會(huì)帶來(lái)原子性問(wèn)題,原子性問(wèn)題是什么意思呢?你首先就要先理解原子。在生物里面原子叫做不可再分的物質(zhì)。在軟件里面,原子叫做不可再分的程序操作。而原子性問(wèn)題肯定就是打破了這個(gè)規(guī)則,也就是說(shuō)在這個(gè)操作中又進(jìn)行了拆分。

在緩存一致性問(wèn)題中,兩個(gè)CPU內(nèi)核中a的數(shù)據(jù)不一致,也就是說(shuō)兩個(gè)CPU內(nèi)核讀取主存a的值是不一樣的。那么對(duì)于a的更改這個(gè)操作肯定就不是原子性,在A更改的過(guò)程中,兩個(gè)CPU內(nèi)核同時(shí)進(jìn)行了更改。

(2)可見(jiàn)性問(wèn)題

上面在介紹原子性問(wèn)題的時(shí)候說(shuō)了,兩個(gè)線程(CPU內(nèi)核)訪問(wèn)同一個(gè)變量時(shí),線程2修改了這個(gè)變量的值,但是線程1卻沒(méi)有看到其修改,所以讀的仍然是舊數(shù)據(jù)。

jvm中java內(nèi)存模型的示例分析

(3)有序性

也就是程序沒(méi)有按照指定的順序去執(zhí)行。

可見(jiàn)性問(wèn)題和有序性問(wèn)題就是由于處理器優(yōu)化和執(zhí)行重排造成的。

 

階段四

針對(duì)于這么多問(wèn)題,于是java虛擬機(jī)提出了一個(gè)java內(nèi)存模型。有效地解決了上面出現(xiàn)的這三個(gè)問(wèn)題:

 

三、java內(nèi)存模型

 

1、解釋

為了和開頭進(jìn)行對(duì)照,我們?cè)俳o出以此他的內(nèi)存模型圖:

jvm中java內(nèi)存模型的示例分析

從這張圖我們分析一下java內(nèi)存模型是如何解決上面的三個(gè)問(wèn)題的。

規(guī)則一:所有的數(shù)據(jù)都在主內(nèi)存中。

規(guī)則二:每個(gè)線程都保留一份共享變量的副本。線程對(duì)變量的所有操作都必須在這個(gè)副本內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存。

規(guī)則三:不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行。

看文字有點(diǎn)亂,我們舉個(gè)例子,這個(gè)例子我覺(jué)得不太恰當(dāng),但是結(jié)合著上面的概念,相信你會(huì)明白的。在古代的時(shí)候經(jīng)常發(fā)生旱災(zāi),朝廷去賑災(zāi),于是拿了一個(gè)大鐵鍋煮粥,這個(gè)大鐵鍋就是主內(nèi)存,里面的粥就是數(shù)據(jù)。而每個(gè)難民就代表了不同的線程。

(1)難民都有一個(gè)碗,盛放一碗粥。就好比是每個(gè)線程都有一個(gè)本地內(nèi)存,有一個(gè)主內(nèi)存的副本。

(2)難民喝粥就只能在自己碗里,不能直接爬到鍋中喝粥,就好比線程只能在自己的本地內(nèi)存中操作數(shù)據(jù),不能直接到主內(nèi)存讀寫數(shù)據(jù)。

(3)其中一個(gè)難民想要把粥分給伙伴,怎么辦呢?這個(gè)難民要先把粥倒進(jìn)鍋里,同伴再去鍋里盛。就好比線程不能直接訪問(wèn)對(duì)方的內(nèi)存,他們之間的數(shù)據(jù)傳遞都是通過(guò)主內(nèi)存。

這個(gè)例子希望你能明白。現(xiàn)在這個(gè)模型算是出來(lái)了,還有一個(gè)問(wèn)題沒(méi)有解決,那就是java提供了什么東西來(lái)實(shí)現(xiàn)的這三個(gè)規(guī)則呢?

由于提供的機(jī)制太多,我們可以簡(jiǎn)單的例舉幾個(gè),比如說(shuō)synchronized關(guān)鍵字保證了原子性,volatile關(guān)鍵字保證了可見(jiàn)性。synchronized關(guān)鍵字和volatile關(guān)鍵字保證了有序性。當(dāng)然還有很多的Lock機(jī)制,并發(fā)包里面等等都是為了解決這三個(gè)問(wèn)題提出來(lái)的。

 

2、happens-before原則

其中有一條很重要的規(guī)則叫做happens-before原則,這條原則是為了解決可見(jiàn)性提出來(lái)的。什么意思呢?

如果一個(gè)操作的執(zhí)行結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在happens-before關(guān)系。舉個(gè)例子:

(1)程序順序規(guī)則:一個(gè)線程中的每個(gè)操作發(fā)生在后一個(gè)操作之前,這就是happens-before。

(2)鎖規(guī)則:對(duì)于鎖機(jī)制,一定要先加鎖,才能解鎖,這也是happens-before。

(3)volatile域規(guī)則:對(duì)一個(gè)volatile域的寫操作一定要發(fā)生在讀操作前面。

上面是在程序角度來(lái)看的,舉一個(gè)最簡(jiǎn)單不過(guò)的例子,你必須要做飯,才能夠吃到飯。


感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“jvm中java內(nèi)存模型的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(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