一、介紹
Java內(nèi)存模型即Java Memory Model,簡(jiǎn)稱(chēng)JMM。JMM定義了Java 虛擬機(jī)(JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式。JVM是整個(gè)計(jì)算機(jī)虛擬模型,所以JMM是隸屬于JVM的。
二、內(nèi)存分塊
1、程序計(jì)數(shù)器(寄存器)
2、本地方法棧
3、虛擬機(jī)棧????(重點(diǎn))
4、堆 (重點(diǎn))
5、方法區(qū)和其中的運(yùn)行時(shí)常量池????(重點(diǎn))
三、詳解

3.1、程序計(jì)數(shù)器(寄存器)
????????????????當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼行號(hào)指示器
????????????字節(jié)碼解釋器工作依賴(lài)計(jì)數(shù)器控制完成
????????????通過(guò)執(zhí)行線(xiàn)程行號(hào)記錄,讓線(xiàn)程輪流切換各條線(xiàn)程之間計(jì)數(shù)器互不影響
????????????線(xiàn)程私有,生命周期與線(xiàn)程相同,隨JVM啟動(dòng)而生,JVM關(guān)閉而死
????????????線(xiàn)程執(zhí)行Java方法時(shí),記錄其正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址
???????????? 線(xiàn)程執(zhí)行Nativan方法時(shí),計(jì)數(shù)器記錄為空(Undefined)
???????????? 唯一在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況區(qū)域
????????????在這其中,很多不理解的沒(méi)關(guān)系,我們學(xué)過(guò)多線(xiàn)程,有兩個(gè)線(xiàn)程,其中一個(gè)線(xiàn)程可以暫停使用,讓其他線(xiàn)程運(yùn)行,然后等自己獲得cpu資源時(shí),又能從暫停的地方開(kāi)始運(yùn)行,那么為什么能夠記住暫停的位置的,這就依靠了程序計(jì)數(shù)器, 通過(guò)這個(gè)例子,大概了解一下程序計(jì)數(shù)器的功能。
3.2、本地方法棧
????????????????不知道大家看過(guò)源碼沒(méi)有,看過(guò)的都應(yīng)該知道,很多的算法或者一個(gè)功能的實(shí)現(xiàn),都被java封裝到了本地方法中,程序直接通過(guò)調(diào)用本地的方法就行了,本地方法棧就是用來(lái)存放這種方法的,實(shí)現(xiàn)該功能的代碼可能是C也可能是C++,反正不一定就是java實(shí)現(xiàn)的。上面兩個(gè)不是我們所要學(xué)習(xí)的重點(diǎn),接下來(lái)三個(gè)才是重點(diǎn)。
3.3、虛擬機(jī)棧
????????????????這個(gè)大家都應(yīng)該有所了解,現(xiàn)在來(lái)細(xì)講它,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用來(lái)存放存儲(chǔ)局部變量表、操作數(shù)表、動(dòng)態(tài)連接、方法出口等信息,每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。? ? 這個(gè)話(huà)怎么理解呢?比如執(zhí)行一個(gè)類(lèi)(類(lèi)中有main方法)時(shí),執(zhí)行到main方法,就會(huì)把為main方法創(chuàng)建一個(gè)棧幀,然后在加到虛擬機(jī)棧中,棧幀中會(huì)存放這main方法中的各種局部變量,對(duì)象引用等東西。如圖

????????????????當(dāng)在main方法中調(diào)用別的方法時(shí),就會(huì)有另一個(gè)方法的棧幀入虛擬機(jī)棧,當(dāng)該方法調(diào)用完了之后,彈棧,然后main方法處于棧頂,就繼續(xù)執(zhí)行,直到結(jié)束,然后main方法棧幀也彈棧,程序就結(jié)束了??傊摂M機(jī)棧中就是有很多個(gè)棧幀的入棧出棧,棧幀中存放的都市一些變量名等東西,所以我們平常說(shuō)棧中存放的是一些局部變量,因?yàn)榫植孔兞烤褪窃诜椒ㄖ?。也就是在棧幀中,就是這樣說(shuō)過(guò)來(lái)的。
????????????????以上說(shuō)的三個(gè)都是線(xiàn)程不共享的,也就是這部分內(nèi)存,每個(gè)線(xiàn)程獨(dú)有,不會(huì)讓別的線(xiàn)程訪(fǎng)問(wèn)到,接下來(lái)的兩個(gè)就是線(xiàn)程共享了,也就會(huì)出現(xiàn)線(xiàn)程安全問(wèn)題。
3.4、堆
????????????????所有線(xiàn)程共享的一塊內(nèi)存區(qū)域。Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,因?yàn)樵搩?nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例。幾乎所有的對(duì)象實(shí)例度在這里分配內(nèi)存,也就是通常我們說(shuō)的new對(duì)象,該對(duì)象就會(huì)在堆中開(kāi)辟一塊內(nèi)存來(lái)存放對(duì)象中的一些信息,比如屬性呀什么的。同時(shí)堆也是垃圾收集器管理的主要區(qū)域。因此很多時(shí)候被稱(chēng)為"GC堆"。
3.5、方法區(qū)和其中的運(yùn)行時(shí)常量池
????????????????和堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、和編譯器編譯后的代碼(也就是存儲(chǔ)字節(jié)碼文件。.class)等數(shù)據(jù),這里可以看到常量也會(huì)在方法區(qū)中,是因?yàn)榉椒▍^(qū)中有一個(gè)運(yùn)行時(shí)常量池,為什么叫運(yùn)行時(shí)常量池,因?yàn)樵诰幾g后期生成的是各種字面量(字面量的意思就是值,比如int i=3,這個(gè)3就是字面量的意思)和符號(hào)引用,這些是存放在一個(gè)叫做常量池(這個(gè)常量池是在字節(jié)碼文件中)的地方,當(dāng)類(lèi)加載進(jìn)入方法區(qū)時(shí),就會(huì)把該常量池中的內(nèi)容放入運(yùn)行時(shí)常量池中。這里要注意,運(yùn)行時(shí)常量池和常量池,不要搞混淆了,字節(jié)碼文件中也有常量池,在后面的章節(jié)會(huì)詳細(xì)講解這個(gè)東西?,F(xiàn)在只需要知道方法區(qū)中有一個(gè)運(yùn)行時(shí)常量池,就是用來(lái)存放常量的。還有一點(diǎn),運(yùn)行時(shí)常量池不一定就一定要從字節(jié)碼常量池中拿取常量,可能在程序運(yùn)行期間將新的常量放入池中,比如String.intern()方法,這個(gè)方法的作用就是:先從方法區(qū)的運(yùn)行時(shí)常量池中查找看是否有該值,如果有,則返回該值的引用,如果沒(méi)有,那么就會(huì)將該值加入運(yùn)行時(shí)常量池中。
四、練習(xí)。畫(huà)內(nèi)存圖。
? ? ? ? ? ? ? ? ?平常分析中用到的最多還是堆、虛擬機(jī)棧和方法區(qū)。
?????????????????例如:看下面這段程序,然后畫(huà)出內(nèi)存分析圖

1、首先運(yùn)行程序,Demo1_car.java就會(huì)變?yōu)镈emo1_car.class,將Demo1_car.class加入方法區(qū),檢查是否字節(jié)碼文件常量池中是否有常量值,如果有,那么就加入運(yùn)行時(shí)常量池
2、遇到main方法,創(chuàng)建一個(gè)棧幀,入虛擬機(jī)棧,然后開(kāi)始運(yùn)行main方法中的程序
3、Car c1 = new Car(); 第一次遇到Car這個(gè)類(lèi),所以將Car.java編譯為Car.class文件,然后加入方法區(qū),跟第一步一樣。然后new Car()。就在堆中創(chuàng)建一塊區(qū)域,用于存放創(chuàng)建出來(lái)的實(shí)例對(duì)象,地址為0X001.其中有兩個(gè)屬性值 color和num。默認(rèn)值是null 和 0
4、然后通過(guò)c1這個(gè)引用變量去設(shè)置color和num的值,
5、調(diào)用run方法,然后會(huì)創(chuàng)建一個(gè)棧幀,用來(lái)裝run方法中的局部變量的,入虛擬機(jī)棧,run方法中就打印了一句話(huà),結(jié)束之后,該棧幀出虛擬機(jī)棧。又只剩下main方法這個(gè)棧幀了
6、接著又創(chuàng)建了一個(gè)Car對(duì)象,所以又在堆中開(kāi)辟了一塊內(nèi)存,之后就是跟之前的步驟一樣了。
這樣就分析結(jié)束了,在腦袋中就應(yīng)該有一個(gè)大概的認(rèn)識(shí)對(duì)堆、虛擬機(jī)棧、和方法區(qū)。注意這個(gè)方法區(qū)的名字,并不是就單單裝方法的,能裝很多東西。
五、拓展
1、創(chuàng)建對(duì)象,在堆中開(kāi)辟內(nèi)存時(shí)是如何分配內(nèi)存的?
????????????????兩種方式:指針碰撞和空閑列表。我們具體使用的哪一種,就要看我們虛擬機(jī)中使用的是什么了。
????????????????指針碰撞:假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過(guò)的內(nèi)存度放一邊,空閑的內(nèi)存放另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,所分配內(nèi)存就僅僅是把哪個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的舉例,這種分配方案就叫指針碰撞
????????????????空閑列表:有一個(gè)列表,其中記錄中哪些內(nèi)存塊有用,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,然后更新列表中的記錄。這就叫做空閑列表
2、對(duì)象引用是如何找到我們?cè)诙阎械膶?duì)象實(shí)例的?
????????????????這個(gè)問(wèn)題也可以稱(chēng)為對(duì)象的訪(fǎng)問(wèn)定位問(wèn)題,也有兩種方式。句柄訪(fǎng)問(wèn)和直接指針訪(fǎng)問(wèn)。 畫(huà)兩張圖就明白了。
????????????????句柄訪(fǎng)問(wèn):Java堆中會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,引用變量中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類(lèi)型數(shù)據(jù)各自的具體地址信息

????????????????解釋圖:在棧中有一個(gè)引用變量指向句柄池中一個(gè)句柄的地址,這個(gè)句柄又包含了兩個(gè)地址,一個(gè)對(duì)象實(shí)例數(shù)據(jù),一個(gè)是對(duì)象類(lèi)型數(shù)據(jù)(這個(gè)在方法區(qū)中,因?yàn)轭?lèi)字節(jié)碼文件就放在方法區(qū)中),
????????????????直接指針訪(fǎng)問(wèn):引用變量中存儲(chǔ)的就直接是對(duì)象地址了,如圖所示

????????????????解釋?zhuān)涸诙阎芯筒粫?huì)分句柄池了,直接指向了對(duì)象的地址,對(duì)象中包含了對(duì)象類(lèi)型數(shù)據(jù)的地址。
區(qū)別:這兩種各有各的優(yōu)勢(shì),
????????????????使用句柄來(lái)訪(fǎng)問(wèn)的最大好處就是引用變量中存儲(chǔ)的是穩(wěn)定的句柄地址,對(duì)象被移動(dòng)(在垃圾收集時(shí)移動(dòng)對(duì)象是很普通的行為)時(shí)就會(huì)改變句柄中實(shí)力數(shù)據(jù)指針,但是引用變量所指向的地址不用改變。
????????????????而使用直接指針訪(fǎng)問(wèn)方式最大的好處就是速度更快,節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo),但是在對(duì)象被移動(dòng)時(shí),又需要改變引用變量的地址。在我們上面分析的例子中,就是使用的直接指針訪(fǎng)問(wèn)的方式。