寫在前面
讀完本篇文章你將知道:
Java的內(nèi)存模型。
Java的內(nèi)存分區(qū)。
全局變量、局部變量、對(duì)象、實(shí)例再內(nèi)存中的位置。
JVM重排序機(jī)制。
JVM的原子性、可見(jiàn)性、有序性。
徹底了解Volatile關(guān)鍵字。
一. Java的內(nèi)存模型
Java內(nèi)存模型即Java Memory Model,簡(jiǎn)稱JMM。JMM定義了Java 虛擬機(jī)(JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式。JVM是整個(gè)計(jì)算機(jī)虛擬模型,所以JMM是隸屬于JVM的。想要掌握J(rèn)ava并非線程JMM一定要了解。Java內(nèi)存模型定義了多線程之間共享變量的可見(jiàn)性以及如何在需要的時(shí)候?qū)蚕碜兞窟M(jìn)行同步。這里的涉及到共享內(nèi)存區(qū)域,稍后會(huì)在Java的內(nèi)存分區(qū)中介紹到。簡(jiǎn)單的說(shuō)就是解釋了一個(gè)問(wèn)題:當(dāng)多個(gè)線程再訪問(wèn)同一個(gè)變量的時(shí)候,其中一個(gè)線程改變了該變量的值但是并未寫入主存中,那么其他線程就會(huì)讀取到舊值,無(wú)法獲取到最新的值。好了接下來(lái)看看什么是內(nèi)存模型:
Java內(nèi)存模型定義了線程和主存(可以理解為java內(nèi)存分區(qū)中的共享區(qū)域,稍后將介紹)之間的抽象關(guān)系:線程之間的共享變量存貯再主存中,每個(gè)線程都會(huì)擁有屬于自己的私有工作內(nèi)存(這個(gè)內(nèi)存分配再棧里面),再工作內(nèi)存中只會(huì)存儲(chǔ)該線程使用到的共享變量的副本。這里的私有工作內(nèi)存其實(shí)是一個(gè)抽象的概念,它包括了緩存、寫緩沖區(qū)、寄存器等區(qū)域。Java內(nèi)存模型控制線程間的通信,它決定一個(gè)線程對(duì)主存共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。這是Java內(nèi)存模型抽象圖:
從圖中我們能分析出:
1.每個(gè)線程再執(zhí)行的時(shí)候都會(huì)有自己的工作內(nèi)存,其中包括了方法里面所包含的所有變量等。
2.每個(gè)線程的私有工作內(nèi)存是不能相互訪問(wèn)的,這也就解釋了為什么我們不能再一個(gè)方法中訪問(wèn)另一個(gè)方法的局部變量。
3.當(dāng)線程想要訪問(wèn)共享變量的時(shí)候,需要從主存中獲取,再自己的方法區(qū)中只是保存的變量的副本。
4.當(dāng)我們修改完共享變量的時(shí)候,需要把改過(guò)的變量寫入主存中,這樣才能讓其他線程獲取到正確的值。
簡(jiǎn)單一點(diǎn)就是:
(1)線程A把線程A本地內(nèi)存中更新過(guò)的共享變量刷新到貯存中去。
(2)線程B到主存中去讀取線程A之前已更新過(guò)的共享變量的的值。
也就是:
int i= 1;
也就是說(shuō),這句代碼被線程執(zhí)行的時(shí)候是這樣的,執(zhí)行線程先把變量i的值的一個(gè)副本存放到自己的工作內(nèi)存中,然后再寫入主存中,而不是直接寫入到主存中。
這樣是不是就可以說(shuō)明同一個(gè)不同的變量作為標(biāo)記去打斷線程是不嚴(yán)謹(jǐn)?shù)模蠹铱梢砸撇降轿业纳弦黄恼氯绾握_的打斷線程。
二. Java的內(nèi)存分區(qū)
一般來(lái)說(shuō),Java程序在運(yùn)行時(shí)會(huì)涉及到以下內(nèi)存區(qū)域:
寄存器:
JVM內(nèi)部虛擬寄存器,存取速度非常快,程序不可控制。
Java虛擬機(jī)棧(通俗就是我們常說(shuō)的“棧”):
它是線程私有的,它的生命周期與線程相同。每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀(StackFrame)用于存儲(chǔ)局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。它存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用(reference類型),它不等同于對(duì)象本身,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn),它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苤赶蛞粋€(gè)代表對(duì)象的句柄或者其他與此對(duì)象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。
堆:
ava堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。這一點(diǎn)在Java虛擬機(jī)規(guī)范中的描述是:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配。 Java堆是垃圾收集器管理的主要區(qū)域。
方法區(qū):
方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開(kāi)來(lái),但它還是屬于堆里面的。
常量池(其實(shí)是方法區(qū)的一部分):
JVM為每個(gè)已加載的類型維護(hù)一個(gè)常量池,常量池就是這個(gè)類型用到的常量的一個(gè)有序集合。包括直接常量(基本類型,String)和對(duì)其他類型、方法、字段的符號(hào)引用(1)。池中的數(shù)據(jù)和數(shù)組一樣通過(guò)索引訪問(wèn)。由于常量池包含了一個(gè)類型所有的對(duì)其他類型、方法、字段的符號(hào)引用,所以常量池在Java的動(dòng)態(tài)鏈接中起了核心作用。常量池存在于堆中.
需要注意的一些:
對(duì)于一個(gè)對(duì)象的成員方法,這些方法中包含本地變量的話,仍需要存儲(chǔ)在棧區(qū),即使它們所屬的對(duì)象還在堆區(qū)里面.等于說(shuō)是對(duì)象所擁有的方法里面涉及創(chuàng)建的變量存儲(chǔ)在棧里面,方法里面使用到的全局變量是隨著對(duì)象實(shí)例一起存儲(chǔ)在堆里面,再方法中使用的時(shí)候也是通過(guò)對(duì)象的引用到堆里面的相對(duì)位置去訪問(wèn)的.
對(duì)于一個(gè)對(duì)象的成員變量,不管他是原始類型還是包裝類型,都會(huì)被存貯在堆區(qū).
方法區(qū)和堆是一樣,是各個(gè)線程共享的區(qū)域,里面存放java虛擬機(jī)加載的類信息,常亮,靜態(tài)變量,即使編譯器編譯后的代碼等數(shù)據(jù).
當(dāng)調(diào)用一個(gè)對(duì)象的方法時(shí)會(huì)在java(虛擬機(jī)棧)棧里面創(chuàng)建屬于自己的棧空間,方法走完即被釋放
分清什么是實(shí)例什么是對(duì)象。Class a = new Class();此時(shí)a叫實(shí)例,而不能說(shuō)a是對(duì)象。實(shí)例在棧中,對(duì)象在堆中,操作實(shí)例實(shí)際上是通過(guò)實(shí)例的指針間接操作對(duì)象。多個(gè)實(shí)例可以指向同一個(gè)對(duì)象。
那么我們通過(guò)代碼來(lái)進(jìn)一步的認(rèn)識(shí)每個(gè)分區(qū):
public class Persion{
privite String name = “Wang”;
privite static String love = “eat”;
public void init(int age){
if(age < 0){
age = 0;
}
Log.e(TAG,"Name is "+ name+"Age is "+ age);
}
}
首先我們知道 當(dāng)我用 Persion p = new Perison()的時(shí)候,Persion p 這個(gè)引用存貯再棧里面,new Perison()的對(duì)象保存再堆里面,包括name成員變量都在堆里面;love這個(gè)靜態(tài)變量存貯在常量池里面。當(dāng)我們調(diào)用 p.init(10) 的時(shí)候,會(huì)在該線程所在的棧里面開(kāi)創(chuàng)該線程私有的棧內(nèi)存,用來(lái)保存age變量和name共享變量的副本。這里要說(shuō)一下,堆、方法區(qū)被稱為共享區(qū)域,這里面的數(shù)據(jù)才能被多線程所共享。
三. JVM重排序機(jī)制
在虛擬機(jī)層面,為了盡可能減少內(nèi)存操作速度遠(yuǎn)慢于CPU運(yùn)行速度所帶來(lái)的CPU空置的影響,虛擬機(jī)會(huì)按照自己的一些規(guī)則(這規(guī)則后面再敘述)將程序編寫順序打亂——即寫在后面的代碼在時(shí)間順序上可能會(huì)先執(zhí)行,而寫在前面的代碼會(huì)后執(zhí)行——以盡可能充分地利用CPU。拿上面的例子來(lái)說(shuō):假如不是a=1的操作,而是a=new byte[1024*1024],那么它會(huì)運(yùn)行地很慢,此時(shí)CPU是等待其執(zhí)行結(jié)束呢,還是先執(zhí)行下面那句flag=true呢?顯然,先執(zhí)行flag=true可以提前使用CPU,加快整體效率,當(dāng)然這樣的前提是不會(huì)產(chǎn)生錯(cuò)誤(什么樣的錯(cuò)誤后面再說(shuō))。雖然這里有兩種情況:后面的代碼先于前面的代碼開(kāi)始執(zhí)行;前面的代碼先開(kāi)始執(zhí)行,但當(dāng)效率較慢的時(shí)候,后面的代碼開(kāi)始執(zhí)行并先于前面的代碼執(zhí)行結(jié)束。不管誰(shuí)先開(kāi)始,總之后面的代碼在一些情況下存在先結(jié)束的可能。我們看下簡(jiǎn)單的例子:
public void execute(){
**java學(xué)習(xí)群669823128**
int a=0;
int b=1;
int c=a+b;
}
這里a=0,b=1兩句可以隨便排序,不影響程序邏輯結(jié)果。所以程序再運(yùn)行的時(shí)候會(huì)選擇先運(yùn)行int b = 1 ;然后再運(yùn)行 int a=0;但是我們是無(wú)法觀察到的,這確是可能發(fā)生的,這句c=a+b這句必須在前兩句的后面執(zhí)行,所以在他的前后不會(huì)出現(xiàn)重排序。這里我們就簡(jiǎn)單的了解下就可以啦.
四. JVM的原子性、可見(jiàn)性、有序性
原子性
定義:對(duì)基本類型變量的讀取和賦值操作是原子性操作,即這些操作是不可中斷的,要么執(zhí)行完畢,要么就不執(zhí)行。
x =3; //語(yǔ)句1
y =4 //語(yǔ)句2
**java學(xué)習(xí)群669823128**
z = x+y ;//語(yǔ)句3
x++; //語(yǔ)句4
這里面的操作只有語(yǔ)句1和語(yǔ)句2是原子性的操作,語(yǔ)句3,4不是原子性的操作;因?yàn)樵僬Z(yǔ)句3中包括了三個(gè)操作,1是先讀取x的值,2讀取y的值,3將z的值寫入內(nèi)存中。語(yǔ)句4的解釋是一樣的。一般的一個(gè)語(yǔ)句含有多個(gè)操作該語(yǔ)句就不是原子性的操作,只有簡(jiǎn)單的讀取和賦值才是原子性的操作。
可見(jiàn)性
就是指線程之間的可見(jiàn)性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見(jiàn)的。也就是一個(gè)線程修改結(jié)果,另一個(gè)線程馬上就能看到。
有序性
Java內(nèi)存模型允許編譯器和處理器對(duì)指令進(jìn)行重排序,雖然重排序不會(huì)影響到單線程的正確性,但是會(huì)影響到多線程的正確性。
五. Volatile關(guān)鍵字
這里呢Volatile的三個(gè)條件:
1.不保證原子性。
2.保證有序性。
3.保證可見(jiàn)性。
當(dāng)用volatile修飾共享變量的時(shí)候,線程訪問(wèn)到該變量的時(shí)候都回去主存中去取該變量的值,它的工作內(nèi)存中的緩存將失效,這樣就保證了每個(gè)線程訪問(wèn)該變量的時(shí)候都是從主存中讀寫的。這就是為什么使用Volatile關(guān)鍵字來(lái)修飾線程間共享變量。
六. 結(jié)束語(yǔ)
這些也是對(duì)JVM的一些小的探索,希望能給大家?guī)?lái)一點(diǎn)小的幫助,如果喜歡的話請(qǐng)點(diǎn)個(gè)贊再走吧,感興趣的話就點(diǎn)這里這個(gè)關(guān)注吧,之后我會(huì)繼續(xù)給大家?guī)?lái)一下新的見(jiàn)解,或者把通俗易懂的語(yǔ)言來(lái)描述苦澀難懂的原理~
來(lái)了就喜歡一下吧
java學(xué)習(xí)群669823128