JVM是我們成為一名架構(gòu)師必須要掌握的一個(gè)知識(shí)體系,也是我們和一般只會(huì)敲代碼的程序員特別能體現(xiàn)差距的地方。而JVM的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)织中,也就是它的內(nèi)存模型掘猿,是它入門(mén)的第一課病游。下面我將分成3個(gè)方面向大家介紹JVM內(nèi)存模型。
JVM介紹
JVM整體架構(gòu)
JVM內(nèi)存結(jié)構(gòu)
后記
JVM介紹
1.虛擬機(jī)是啥
虛擬機(jī)(Virtual Machine)指通過(guò)軟件模擬的具有完整硬件系統(tǒng)功能的稠通、運(yùn)行在一個(gè)完全隔離環(huán)境中的完整計(jì)算機(jī)系統(tǒng)衬衬。通俗的講就是用軟件模擬的一臺(tái)物理機(jī)。
很多時(shí)候改橘,在我們需要用到多臺(tái)電腦的時(shí)候滋尉,但是手頭緊,就可以使用虛擬機(jī)模擬出新的電腦去使用飞主。
常用的虛擬機(jī)有VMWare(一般多用模擬linux系統(tǒng))狮惜,Virtual Box(做App的模擬),Java Virtual Machine(JVM)
2.JVM是啥
JVM全稱(chēng)是Java Virtual Machine(java虛擬機(jī))碌识,你可以這樣理解:JVM是以java字節(jié)碼(class文件)為機(jī)器碼的物理機(jī)碾篡。java通過(guò)它實(shí)現(xiàn)了一處編譯、處處運(yùn)行的豪言壯語(yǔ)筏餐,任何機(jī)器上只要裝了JVM开泽,我都可以把編譯打包好的java代碼放上去運(yùn)行!?伞穆律!。
3.市面上的JVM
Sun HotSpot VM佩番、BEA JRockit VM众旗、IBM J9 VM、Azul VM趟畏、Apache Harmony贡歧、Google Dalvik VM、Microsoft JVM
這里面用的很多的有Sun HotSpot VM赋秀、BEA JRockit VM利朵、IBM J9 VM,其中前兩個(gè)已經(jīng)被Oracle收購(gòu)了猎莲,后面的那個(gè)绍弟,聽(tīng)說(shuō)Oracle也想收購(gòu)來(lái)著,可是想了想著洼,太貴了樟遣,也就放棄了而叼。
JVM整體架構(gòu)
1.JVM的內(nèi)部結(jié)構(gòu)
JVM分成3個(gè)部分,如下:
類(lèi)加載器:負(fù)責(zé)加載
.class
文件存入運(yùn)行時(shí)數(shù)據(jù)區(qū)運(yùn)行時(shí)數(shù)據(jù)區(qū)(內(nèi)存結(jié)構(gòu)):負(fù)責(zé)存儲(chǔ)運(yùn)行時(shí)的數(shù)據(jù)的地方
執(zhí)行引擎:輸入字節(jié)碼豹悬,執(zhí)行相應(yīng)的指令葵陵,輸出結(jié)果
我們可以把他們3者的關(guān)系看成是,CPU(執(zhí)行引擎)瞻佛,內(nèi)存(運(yùn)行時(shí)數(shù)據(jù)區(qū))脱篙,文件系統(tǒng)驅(qū)動(dòng)(類(lèi)加載器),首先由文件系統(tǒng)的驅(qū)動(dòng)(類(lèi)加載器)去讀取.class
文件伤柄,存入內(nèi)存(運(yùn)行時(shí)數(shù)據(jù)區(qū))中绊困,CPU(執(zhí)行引擎)通過(guò)和內(nèi)存(運(yùn)行時(shí)數(shù)據(jù)區(qū))交互,得到計(jì)算結(jié)果适刀。
2.運(yùn)行時(shí)數(shù)據(jù)區(qū)結(jié)構(gòu)
運(yùn)行時(shí)數(shù)據(jù)區(qū)在java設(shè)計(jì)規(guī)范中分成了五個(gè)部分秤朗,分別是程序計(jì)數(shù)器,Java棧蔗彤,本地方法棧川梅,Java堆以及方法區(qū),介紹如下:
程序計(jì)數(shù)器(線程私有):是一個(gè)指針然遏,指向下一條指令的地址贫途,也就是告訴執(zhí)行引擎,該線程下一步要執(zhí)行的指令待侵,需要空間很小
Java棧(線程私有):Java線程執(zhí)行方法的內(nèi)存模型丢早,一個(gè)線程對(duì)應(yīng)一個(gè)棧,用于存儲(chǔ)java方法中的私有數(shù)據(jù)秧倾,它的生命周期和線程一致怨酝。只要線程一結(jié)束該棧就會(huì)釋放,不需要垃圾回收那先。
本地方法棧(線程私有):和Java棧類(lèi)似农猬,本地方法(native方法),也就是早期用C/C++實(shí)現(xiàn)的方法也有棧售淡,在Execution Engine執(zhí)行時(shí)加載本地方法庫(kù)
Java堆(線程共有):存儲(chǔ)那些對(duì)于占用地址較大斤葱,創(chuàng)建成本比較高的數(shù)據(jù),也就是我們創(chuàng)建的對(duì)象實(shí)例揖闸。
方法區(qū)(線程共有):類(lèi)的所有字段和方法字節(jié)碼揍堕,以及一些特殊方法如構(gòu)造函數(shù),接口代碼也在此定義汤纸。簡(jiǎn)單說(shuō)衩茸,所有定義的方法的信息都保存在該區(qū)域,是類(lèi)加載器存儲(chǔ)class文件信息的地方贮泞,靜態(tài)變量+常量+類(lèi)信息(構(gòu)造方法/接口定義)+運(yùn)行時(shí)常量池都存在方法區(qū)中楞慈。
大家可以發(fā)現(xiàn)幔烛,這里面提到了線程私有和線程共有,指的是對(duì)于每一個(gè)新的線程囊蓝,都會(huì)創(chuàng)建一個(gè)程序技術(shù)器说贝、java棧和本地方法棧,一旦線程結(jié)束慎颗,這些都會(huì)被銷(xiāo)毀,而java堆和方法區(qū)只有在所有的線程都執(zhí)行完的時(shí)候才會(huì)被銷(xiāo)毀的言询。
3.Java棧的內(nèi)存模型
棧俯萎,是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。在java棧內(nèi)运杭,會(huì)對(duì)每一個(gè)執(zhí)行到的java方法都會(huì)創(chuàng)建一個(gè)棧幀夫啊,放入棧內(nèi),一旦棧幀執(zhí)行完畢就會(huì)出棧辆憔,繼續(xù)執(zhí)行最外層的棧幀撇眯,直到所有的棧幀都出棧了,線程也就執(zhí)行完畢了虱咧。
對(duì)于每一個(gè)棧幀熊榛,里面都有4個(gè)部分,分別是局部變量表腕巡、操作數(shù)棧玄坦、動(dòng)態(tài)鏈接以及返回地址;
局部變量表:記錄當(dāng)前方法參數(shù)以及內(nèi)部定義的局部變量
操作數(shù)棧:記錄當(dāng)前的操作數(shù)
動(dòng)態(tài)鏈接:表示對(duì)內(nèi)部方法的調(diào)用绘沉,無(wú)法在類(lèi)加載階段或第一次使用時(shí)直接確定的煎楣,由每一次調(diào)用所確定方法引用,稱(chēng)之為動(dòng)態(tài)鏈接
返回地址:方法結(jié)束车伞,返回上層方法以及恢復(fù)上層方法執(zhí)行狀態(tài)所需的信息
4. Java堆介紹
為了能夠更好的對(duì)對(duì)象進(jìn)行垃圾回收择懂,java設(shè)計(jì)對(duì)堆進(jìn)行了分代,這樣可以方便對(duì)存活時(shí)間不同的對(duì)象進(jìn)行處理另玖,分成了2個(gè)部分:
年輕代:用于存放新產(chǎn)生的對(duì)象困曙。年輕代分為兩部分: 伊甸區(qū)(Eden space)和幸存者區(qū)(Survivor pace) 。所有的類(lèi)都是在伊甸區(qū)被new出來(lái)的日矫。幸存區(qū)有兩個(gè): 0區(qū)(Survivor 0 space)和1區(qū)(Survivor 1 space)赂弓。當(dāng)伊甸園的空間用完時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象哪轿,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC)盈魁,將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷(xiāo)毀。然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存 0區(qū)窃诉。若幸存 0區(qū)也滿(mǎn)了杨耙,再對(duì)該區(qū)進(jìn)行垃圾回收赤套,然后移動(dòng)到1區(qū),如果還是滿(mǎn)了珊膜,就到老年代容握。
老年代:用于存放被長(zhǎng)期引用的對(duì)象。新生區(qū)經(jīng)過(guò)多次GC仍然存活的對(duì)象移動(dòng)到老年區(qū)车柠。若老年區(qū)也滿(mǎn)了剔氏,那么這個(gè)時(shí)候?qū)a(chǎn)生MajorGC(FullGC),進(jìn)行老年區(qū)的內(nèi)存清理竹祷。若老年區(qū)執(zhí)行了Full GC之后發(fā)現(xiàn)依然無(wú)法進(jìn)行對(duì)象的保存谈跛,就會(huì)產(chǎn)生OOM異常“OutOfMemoryError塑陵。
5. 方法區(qū)介紹
雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分感憾,但是它卻有一個(gè)別名叫做 Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開(kāi)來(lái)令花。所以這一部分我也就單獨(dú)拿出來(lái)說(shuō)明了阻桅。
可能大家對(duì)這一個(gè)名字聽(tīng)的不是很多,但可能大家聽(tīng)過(guò)永久代或持久代的會(huì)更多一點(diǎn)兼都,永久代是HotSpot JVM 在java8之前對(duì)方法區(qū)的實(shí)現(xiàn)嫂沉,這個(gè)名稱(chēng)也是相對(duì)年輕代和老年代來(lái)說(shuō),他們的FullGC也會(huì)對(duì)永生代進(jìn)行垃圾回收扮碧。
在java8之后输瓜,永久代被代替了,換成了元空間區(qū)芬萍,兩者本質(zhì)都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)尤揣,區(qū)別在于元數(shù)據(jù)區(qū)并不在虛擬機(jī)中,而是使用本地物理內(nèi)存柬祠,這樣的話北戏,方法區(qū)的大小也就不那么容易收到限制,而永久代在虛擬機(jī)中漫蛔,永久代邏輯結(jié)構(gòu)上屬于堆嗜愈,但是物理上不屬于堆,堆大小=新生代+老年代莽龟。元數(shù)據(jù)區(qū)也有可能發(fā)生OutOfMemory異常蠕嫁,
元數(shù)據(jù)區(qū)的動(dòng)態(tài)擴(kuò)展,默認(rèn)–XX:MetaspaceSize值為21MB的高水位線毯盈。一旦觸及則Full GC將被觸發(fā)并卸載沒(méi)有用的類(lèi)(類(lèi)對(duì)應(yīng)的類(lèi)加載器不再存活)剃毒,然后高水位線將會(huì)重置。新的高水位線的值取決于GC后釋放的元空間。如果釋放的空間少赘阀,這個(gè)高水位線則上升益缠。如果釋放空間過(guò)多,則高水位線下降基公。
后記
1.為什么jdk1.8用元數(shù)據(jù)區(qū)取代了永久代幅慌?
首先我們來(lái)看看官方解釋?zhuān)阂瞥谰么菫槿诤螲otSpot JVM與 JRockit VM而做出的努力,因?yàn)镴Rockit沒(méi)有永久代轰豆,不需要配置永久代胰伍;
除此之外,我覺(jué)得應(yīng)該還有其他考慮:去除永久代后分代GC可以減少?gòu)?fù)雜性酸休,不用考慮永久代的gc喇辽;不用指定PermSize,指定太小容易造成永久代OOM
2.既然有動(dòng)態(tài)鏈接雨席,那靜態(tài)鏈接呢?
解析調(diào)用一定是個(gè)靜態(tài)過(guò)程吠式,在編譯期間就完全確定陡厘,在類(lèi)加載的解析階段就會(huì)把涉及的符號(hào)引用轉(zhuǎn)化為可確定的直接引用,不會(huì)延遲到運(yùn)行期再去完成特占,這也就是Java中的靜態(tài)鏈接糙置。
在Class文件中的常量持中存有大量的符號(hào)引用。字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用作為參數(shù)是目。這些符號(hào)引用一部分在類(lèi)的加載階段(解析)或第一次使用的時(shí)候就轉(zhuǎn)化為了直接引用(指向數(shù)據(jù)所存地址的指針或句柄等)谤饭,這種轉(zhuǎn)化稱(chēng)為靜態(tài)鏈接。而相反的懊纳,另一部分在運(yùn)行期間轉(zhuǎn)化為直接引用揉抵,就稱(chēng)為動(dòng)態(tài)鏈接。
通俗一點(diǎn)就是說(shuō)能在類(lèi)加載階段就能確定的方法調(diào)用嗤疯,是靜態(tài)鏈接冤今,如果要在運(yùn)行時(shí)確定的話,那就是動(dòng)態(tài)鏈接了茂缚,我們通過(guò)這樣的特性可以實(shí)現(xiàn)多態(tài)戏罢。
后面我們將繼續(xù)編寫(xiě)幾篇jvm類(lèi)加載以及調(diào)優(yōu)等相關(guān)知識(shí),敬請(qǐng)期待哦脚囊。如果我有寫(xiě)的不對(duì)的地方龟糕,歡迎大家來(lái)指正,讓我們?cè)诨ヂ?lián)網(wǎng)架構(gòu)師之路上越走越遠(yuǎn)悔耘,感謝大家的陪伴讲岁,謝謝大家啦~
文章轉(zhuǎn)載自微信公眾號(hào),互聯(lián)網(wǎng)架構(gòu)師學(xué)習(xí)之路,歡迎大家的關(guān)注催首,我們?cè)倩ヂ?lián)網(wǎng)架構(gòu)師學(xué)習(xí)之路上等你扶踊!