您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java內(nèi)存模型JMM的介紹”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Java內(nèi)存模型JMM的介紹”吧!
在現(xiàn)代多核處理器中,每個(gè)處理器都有自己的緩存,需要定期的與主內(nèi)存進(jìn)行協(xié)調(diào)。
想要確保每個(gè)處理器在任意時(shí)刻知道其他處理器正在進(jìn)行的工作,將需要很大的開(kāi)銷,且通常是沒(méi)必要的。
1、 由于計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算能力之間有幾個(gè)數(shù)量級(jí)的差距,所以現(xiàn)代計(jì)算機(jī)系統(tǒng)都不得不加入一層讀寫速度盡可能接近處理器運(yùn)算速度的高速緩存(cache)來(lái)作為內(nèi)存與處理器之間的緩沖:將運(yùn)算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速進(jìn)行,當(dāng)運(yùn)算結(jié)束后再?gòu)木彺嫱交貎?nèi)存之中沒(méi)這樣處理器就無(wú)需等待緩慢的內(nèi)存讀寫了。
2、多個(gè)處理器運(yùn)算任務(wù)都涉及同一塊主存,需要一種協(xié)議可以保障數(shù)據(jù)的一致性,這類協(xié)議有MSI、MESI、MOSI及Dragon Protocol等。Java虛擬機(jī)內(nèi)存模型中定義的內(nèi)存訪問(wèn)操作與硬件的緩存訪問(wèn)操作是具有可比性的。
3、基于高速緩存的存儲(chǔ)交互很好地解決了處理器與內(nèi)存的速度矛盾,但是引入了一個(gè)新的問(wèn)題:
緩存一致性(Cache Coherence)。在多處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存,而他們又共享同一主存,下面會(huì)介紹這個(gè)問(wèn)題
因?yàn)镃PU的頻率太快了,快到主存跟不上,這樣在處理器時(shí)鐘周期內(nèi),CPU常常需要等待主存,浪費(fèi)資源。CPU往往需要重復(fù)處理相同的數(shù)據(jù)、重復(fù)執(zhí)行相同的指令,如果這部分?jǐn)?shù)據(jù)、指令CPU能在CPU緩存中找到,CPU就不需要從內(nèi)存或硬盤中再讀取數(shù)據(jù)、指令,從而減少了整機(jī)的響應(yīng)時(shí)間,所以cache的出現(xiàn),是為了緩解CPU和內(nèi)存之間速度的不匹配問(wèn)題(結(jié)構(gòu):cpu -> cache -> memory)
在程序執(zhí)行的過(guò)程中就變成了:
當(dāng)程序在運(yùn)行過(guò)程中,會(huì)將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計(jì)算時(shí)就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當(dāng)運(yùn)算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。
在Intel官網(wǎng)上產(chǎn)品-處理器界面內(nèi)對(duì)緩存的定義為:CPU高速緩存是處理器上的一個(gè)快速記憶區(qū)域。英特爾智能高速緩存(SmartCache)是指可讓所有內(nèi)核動(dòng)態(tài)共享最后一級(jí)高速緩存的架構(gòu)。這里就提及到了最后一級(jí)高速緩存的概念,即為CPU緩存中的L3(三級(jí)緩存),那么我們繼續(xù)來(lái)解釋一下什么叫三級(jí)緩存,分別又是指哪三級(jí)緩存。
1) 三級(jí)緩存(L1一級(jí)緩存、L2二級(jí)緩存、L3三級(jí)緩存)都是集成在CPU內(nèi)的緩存 2) 它們的作用都是作為CPU與主內(nèi)存之間的高速數(shù)據(jù)緩沖區(qū) 3) L1最靠近CPU核心,L2其次,L3再次 運(yùn)行速度方面:L1最快、L2次快、L3最慢
容量大小方面:L1最小、L2較大、L3最大
4) CPU會(huì)先在最快的L1中尋找需要的數(shù)據(jù),找不到再去找次快的L2,還找不到再去找L3,L3都沒(méi)有那就只能去內(nèi)存找了。
5) 單核CPU只含有一套L1,L2,L3緩存;如果CPU含有多個(gè)核心,即多核CPU,則每個(gè)核心都含有一套L1(甚至和L2)緩存,而共享L3(或者和L2)緩存。
在單線程環(huán)境下,cpu核心的緩存只被一個(gè)線程訪問(wèn)。緩存獨(dú)占,不會(huì)出現(xiàn)訪問(wèn)沖突等問(wèn)題在多線程場(chǎng)景下,在CPU和主存之間增加緩存,就可能存在緩存一致性問(wèn)題,也就是說(shuō),在多核CPU中,每個(gè)核的自己的緩存中,關(guān)于同一個(gè)數(shù)據(jù)的緩存內(nèi)容可能不一致,這也就是我們上面提到的緩存一致性的問(wèn)題
從java源碼到最終實(shí)際執(zhí)行的指令序列,會(huì)經(jīng)歷下面3種重排序:
重排序的現(xiàn)象:
a=10,b=a 這一組 b依賴a,不會(huì)重排序
a=10,b=50 這一組 a和b 沒(méi)有關(guān)系,那么就有可能被重排序執(zhí)行 b=50,a=10
cpu和編譯器為了提高程序的執(zhí)行效率會(huì)按照一定的規(guī)則允許指令優(yōu)化,不影響單線程程序執(zhí)行結(jié)果,但是多線程就會(huì)影響程序結(jié)果
Java內(nèi)存模型即Java Memory Model,簡(jiǎn)稱JMM。JMM定義了Java 虛擬機(jī)(JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式。JVM是整個(gè)計(jì)算機(jī)虛擬模型,所以JMM是隸屬于JVM的。
Java內(nèi)存模型(Java Memory Model ,JMM)就是一種符合內(nèi)存模型規(guī)范的,屏蔽了各種硬件和操作系統(tǒng)的訪問(wèn)差異的,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問(wèn)都能保證效果一致的機(jī)制及規(guī)范??梢员苊庀馽++等直接使用物理硬件和操作系統(tǒng)的內(nèi)存模型在不同操作系統(tǒng)和硬件平臺(tái)下表現(xiàn)不同,比如有些c/c++程序可能在windows平臺(tái)運(yùn)行正常,而在linux平臺(tái)卻運(yùn)行有問(wèn)題。
注意JMM與JVM內(nèi)存區(qū)域劃分的區(qū)別: JMM描述的是一組規(guī)則,圍繞原子性、有序性和可見(jiàn)性展開(kāi); 相似點(diǎn):存在共享區(qū)域和私有區(qū)域
Java線程之間的通信采用的是過(guò)共享內(nèi)存模型,這里提到的共享內(nèi)存模型指的就是Java內(nèi)存模型(簡(jiǎn)稱JMM),JMM決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。從抽象的角度來(lái)看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。
從上圖來(lái)看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個(gè)步驟:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
線程A把本地內(nèi)存A中更新過(guò)的共享變量刷新到主內(nèi)存中去。
線程B到主內(nèi)存中去讀取線程A之前已更新過(guò)的共享變量。具體示意圖:
如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量z的副本。假設(shè)初始時(shí),這三個(gè)內(nèi)存中的z值都為0。線程A在執(zhí)行時(shí),把更新后的z值(假設(shè)值為1)臨時(shí)存放在自己的本地內(nèi)存A中。當(dāng)線程A和線程B需要通信時(shí),線程A首先會(huì)把自己本地內(nèi)存中修改后的z值刷新到主內(nèi)存中,此時(shí)主內(nèi)存中的z值變?yōu)榱?。隨后,線程B到主內(nèi)存中去讀取線程A更新后的z值,此時(shí)線程B的本地內(nèi)存的z值也變?yōu)榱?。
從整體來(lái)看,這兩個(gè)步驟實(shí)質(zhì)上是線程A在向線程B發(fā)送消息,而且這個(gè)通信過(guò)程必須要經(jīng)過(guò)主內(nèi)存。JMM通過(guò)控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互,來(lái)為java程序員提供內(nèi)存可見(jiàn)性保證。
在JVM內(nèi)部,Java 內(nèi)存模型把 Java 虛擬機(jī)內(nèi)部劃分為:線程棧和堆
線程棧:
每一個(gè)運(yùn)行在 Java 虛擬機(jī)里的線程都擁有自己的線程棧。這個(gè)線程棧包含了這個(gè)線程調(diào)用的方法當(dāng)前執(zhí)行點(diǎn)相關(guān)的信息。一個(gè)線程僅能訪問(wèn)自己的線程棧。一個(gè)線程創(chuàng)建的本地變量對(duì)其它線程不可見(jiàn),僅自己可見(jiàn)。即使兩個(gè)線程執(zhí)行同樣的代碼,這兩個(gè)線程任然在在自己的線程棧中的代碼來(lái)創(chuàng)建本地變量。因此,每個(gè)線程擁有每個(gè)本地變量的獨(dú)有版本。
線程堆:
堆上包含在 Java 程序中創(chuàng)建的所有對(duì)象,無(wú)論是哪一個(gè)對(duì)象創(chuàng)建的。這包括原始類型的對(duì)象版本。如果一個(gè)對(duì)象被創(chuàng)建然后賦值給一個(gè)局部變量,或者用來(lái)作為另一個(gè)對(duì)象的成員變量,這個(gè)對(duì)象任然是存放在堆上。
一個(gè)本地變量如果是原始類型,那么它會(huì)被完全存儲(chǔ)到棧區(qū)
一個(gè)本地變量也有可能是一個(gè)對(duì)象的引用,這種情況下,這個(gè)本地引用會(huì)被存儲(chǔ)到棧中,但是對(duì)象本身仍然存儲(chǔ)在堆區(qū)
對(duì)于一個(gè)對(duì)象的成員方法,這些方法中包含本地變量,仍需要存儲(chǔ)在棧區(qū),即使它們所屬的對(duì)象在堆區(qū)
對(duì)于一個(gè)對(duì)象的成員變量,不管它是原始類型還是包裝類型,都會(huì)被存儲(chǔ)到堆區(qū)
Static類型的變量以及類本身相關(guān)信息都會(huì)隨著類本身存儲(chǔ)在堆區(qū)
堆中的對(duì)象可以被多線程共享。如果一個(gè)線程獲得一個(gè)對(duì)象的應(yīng)用,它便可訪問(wèn)這個(gè)對(duì)象的成員變量。如果兩個(gè)線程同時(shí)調(diào)用了同一個(gè)對(duì)象的同一個(gè)方法,那么這兩個(gè)線程便可同時(shí)訪問(wèn)這個(gè)對(duì)象的成員變量,但是對(duì)于本地變量,每個(gè)線程都會(huì)拷貝一份到自己的線程棧中
Java內(nèi)存模型和硬件內(nèi)存架構(gòu)并不一致。硬件內(nèi)存架構(gòu)中并沒(méi)有區(qū)分棧和堆,從硬件上看,不管是棧還是堆,大部分?jǐn)?shù)據(jù)都會(huì)存到主存中,當(dāng)然一部分棧和堆的數(shù)據(jù)也有可能會(huì)存到CPU寄存器中,如下圖所示,Java內(nèi)存模型和計(jì)算機(jī)硬件內(nèi)存架構(gòu)是一個(gè)交叉關(guān)系:
1) lock(鎖定):作用于主內(nèi)存的變量,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài)
2) unock(解鎖):作用于主內(nèi)存的變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定
3) read(讀取):作用于主內(nèi)存的變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用
4) load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
5) use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎
6) assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量
7) store(存儲(chǔ)):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到內(nèi)存中,以便隨后的write的操作
8) write(寫入):作用于工作內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中
如果要把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需要按順序地執(zhí)行read和load操作,如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序地執(zhí)行store和write操作。但Java內(nèi)存模型只要求上訴操作必須按順序執(zhí)行,而沒(méi)有保證必須是連續(xù)執(zhí)行
不允許read和load、store和write操作之一單獨(dú)出現(xiàn)
不允許一個(gè)線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中
不允許一個(gè)線程無(wú)原因的(沒(méi)有發(fā)生過(guò)任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中
一個(gè)新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量。即就是對(duì)一個(gè)變量實(shí)施use和store操作之前,必須先執(zhí)行過(guò)了assign和load操作
一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)解鎖。lock和unlock必須成對(duì)出現(xiàn)
如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
如果一個(gè)變量事先沒(méi)有被lock操作鎖定,則不允許對(duì)它執(zhí)行unlock操作,也不允許去unlock一個(gè)被其他線程鎖定的變量
對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)
原子性、可見(jiàn)性、有序性:可以查看我上一篇文章:線程安全性詳解(原子性、可見(jiàn)性、有序性)
優(yōu)勢(shì):1) 速度:使用處理多個(gè)請(qǐng)求,響應(yīng)更快,復(fù)雜的操作可以分成多個(gè)進(jìn)程同時(shí)執(zhí)行 2) 設(shè)計(jì):程序設(shè)計(jì)在某些情況下更簡(jiǎn)單,也可以有更多的選擇 3) 資源利用:CPU能夠在等待IO的時(shí)候做一些其他的事情
風(fēng)險(xiǎn):1) 安全性:多個(gè)線程共享數(shù)據(jù)時(shí)可能會(huì)產(chǎn)生于期望不相符的結(jié)果 2) 活躍性:某個(gè)操作無(wú)法繼續(xù)進(jìn)行下去時(shí),就會(huì)發(fā)生活躍性問(wèn)題。比如死鎖、饑餓等問(wèn)題 3) 性能:線程過(guò)多時(shí)會(huì)使得:CPU頻繁切換,調(diào)度時(shí)間增多;同步機(jī)制;消耗過(guò)多內(nèi)存
CPU多級(jí)緩存:緩存一致性、亂序執(zhí)行優(yōu)化 Java內(nèi)存模型:JMM規(guī)定、抽象結(jié)構(gòu)、同步八種操作及規(guī)則 Java并發(fā)的優(yōu)勢(shì)與風(fēng)險(xiǎn)
感謝各位的閱讀,以上就是“Java內(nèi)存模型JMM的介紹”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Java內(nèi)存模型JMM的介紹這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(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)容。