最近比較粗淺的接觸了一下JVM,發(fā)現(xiàn)有很多東西還是非常有意思的,并不像之前的印象,覺(jué)得JVM相關(guān)的東西生澀難懂。本文主要記錄這段時(shí)間內(nèi)對(duì)JVM的接觸,主要包括這么幾個(gè)內(nèi)容:
JVM結(jié)構(gòu)及內(nèi)存管理機(jī)制
JVM垃圾回收常見(jiàn)算法
各種垃圾回收器對(duì)比分析
垃圾回收器參數(shù)匯總
1. JVM組成結(jié)構(gòu)
JVM主要由3部分組成,分別是類加載子系統(tǒng)(ClassLoader),執(zhí)行引擎(Execute Engine),運(yùn)行數(shù)據(jù)區(qū)域(Runtime Data Area)。
1.1 類加載器
類加載器負(fù)責(zé)對(duì)Class文件的裝載工作,JVM內(nèi)部對(duì)ClassLoader也有一套完整的體系結(jié)構(gòu),ClassLoader主要分為以下幾種:
Bootstrap ClassLoader
啟動(dòng)類加載器,Classloader體系的根節(jié)點(diǎn),其他ClassLoader都是通過(guò)直接或間接繼承至它,它在JVM啟動(dòng)時(shí)加載,主要加載<JAVA_HOME>\lib,或是-Xbootclasspath參數(shù)指定的路徑中的,并且可以被虛擬機(jī)識(shí)別(僅僅按照文件名識(shí)別的)的類庫(kù)到虛擬機(jī)內(nèi)存中。Extension ClassLoader
擴(kuò)展類加載器,繼承于Bootstrap,主要負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)。Application ClassLoader
應(yīng)用程序類加載器,繼承至擴(kuò)展類加載器,主要負(fù)責(zé)加載ClassPath路徑上的類庫(kù),如果應(yīng)用程序沒(méi)有自定義自己類加載器,則這個(gè)就是默認(rèn)的類加載器。
類加載器采用雙親委派模型工作,如果一個(gè)類加載器收到一個(gè)類加載的請(qǐng)求,它首先將這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次類加載器都是如此,則所有的類加載請(qǐng)求都會(huì)傳送到頂層的啟動(dòng)類加載器,只有父加載器無(wú)法完成這個(gè)加載請(qǐng)求(即它的搜索范圍中沒(méi)有找到所要的類),子類才嘗試加載。這樣做的好處有兩點(diǎn):1)可以避免重復(fù)加載,2)安全角度考慮,防止用戶自定義類加載器替代Java的核心API。
1.2 運(yùn)行數(shù)據(jù)區(qū)
運(yùn)行數(shù)據(jù)區(qū)實(shí)際上就是JVM的內(nèi)存管理區(qū),它主要分為5個(gè)部分,分別是:
方法區(qū)(Method Area)
方法區(qū)主要存放類信息,類的靜態(tài)變量,常量,屬性,方法等信息。堆(heap)
所有通過(guò)new操作創(chuàng)建的對(duì)象的內(nèi)存都在堆中分配。堆又被劃分為新生代(Young Generation)和舊生代(Tenured Generation)。新生代又被進(jìn)一步劃分為Eden和Survivor區(qū),最后Survivor由From和To組成,新建的對(duì)象都是用新生代的Eden分配內(nèi)存,Eden空間不足的時(shí)候,會(huì)把存活的對(duì)象轉(zhuǎn)移到Survivor中,新生代大小可以由-Xmn來(lái)控制,也可以用-XX:SurvivorRatio來(lái)控制Eden和Survivor的比例舊生代。eden,from ,to的默認(rèn)比例是8:1:1。棧(Stack)
每個(gè)線程執(zhí)行每個(gè)方法的時(shí)候都會(huì)在棧中申請(qǐng)一個(gè)棧幀,每個(gè)棧幀包括局部變量區(qū)和操作數(shù)棧,用于存放此次方法調(diào)用過(guò)程中的臨時(shí)變量、參數(shù)和中間結(jié)果程序計(jì)數(shù)器(Program Counter Register)
本地方法棧(Native Method Stack)
用于支持native方法的執(zhí)行,存儲(chǔ)了每個(gè)native方法調(diào)用的狀態(tài)。
2. JVM垃圾回收算法
JVM垃圾回收要經(jīng)過(guò)兩個(gè)主要過(guò)程,垃圾的收集和垃圾的回收,對(duì)于垃圾收集,主要有以下兩種算法:
2.1 垃圾收集
2.1.1 引用計(jì)數(shù)算法
在JDK1.2之前,使用的是引用計(jì)數(shù)器算法,即當(dāng)這個(gè)類被加載到內(nèi)存以后,就會(huì)產(chǎn)生方法區(qū),堆棧、程序計(jì)數(shù)器等一系列信息,當(dāng)創(chuàng)建對(duì)象的時(shí)候,為這個(gè)對(duì)象在堆??臻g中分配對(duì)象,同時(shí)會(huì)產(chǎn)生一個(gè)引用計(jì)數(shù)器,同時(shí)引用計(jì)數(shù)器+1,當(dāng)有新的引用的時(shí)候,引用計(jì)數(shù)器繼續(xù)+1,而當(dāng)其中一個(gè)引用銷毀的時(shí)候,引用計(jì)數(shù)器-1,當(dāng)引用計(jì)數(shù)器被減為零的時(shí)候,標(biāo)志著這個(gè)對(duì)象已經(jīng)沒(méi)有引用了,可以回收了!
,但是隨著業(yè)務(wù)的發(fā)展,很快出現(xiàn)了一個(gè)問(wèn)題當(dāng)我們的代碼出現(xiàn)下面的情形時(shí),該算法將無(wú)法適應(yīng):
ObjA.obj = ObjB
ObjB.obj = ObjA
這樣的代碼會(huì)產(chǎn)生如下引用情形 objA指向objB,而objB又指向objA,這樣當(dāng)其他所有的引用都消失了之后,objA和objB還有一個(gè)相互的引用,也就是說(shuō)兩個(gè)對(duì)象的引用計(jì)數(shù)器各為1,而實(shí)際上這兩個(gè)對(duì)象都已經(jīng)沒(méi)有額外的引用,已經(jīng)是垃圾了。
2.1.2 根搜索算法
根搜索算法是從離散數(shù)學(xué)中的圖論引入的,程序把所有的引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn)GC ROOT開(kāi)始,尋找對(duì)應(yīng)的引用節(jié)點(diǎn),找到這個(gè)節(jié)點(diǎn)以后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn),當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒(méi)有被引用到的節(jié)點(diǎn),即無(wú)用的節(jié)點(diǎn)。
目前java中可作為GC Root的對(duì)象有:
1、 虛擬機(jī)棧中引用的對(duì)象(本地變量表)
2、 方法區(qū)中靜態(tài)屬性引用的對(duì)象
3、 方法區(qū)中常量引用的對(duì)象
4、 本地方法棧中引用的對(duì)象(Native對(duì)象)
2.2 垃圾回收算法
對(duì)于收集到的垃圾,JVM是采用什么算法進(jìn)行回收的呢?主要有這么幾種:
- 標(biāo)記-清除算法
- 復(fù)制算法
- 標(biāo)記-整理算法
2.2.1 標(biāo)記清除算法
標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對(duì)存活的對(duì)象對(duì)象標(biāo)記,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象,進(jìn)行回收,如圖所示。
標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng),并且僅對(duì)不存活的對(duì)象進(jìn)行處理,在存活對(duì)象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象,因此會(huì)造成內(nèi)存碎片!

2.2.2 復(fù)制算法
復(fù)制算法采用從根集合掃描,并將存活對(duì)象復(fù)制到一塊新的,沒(méi)有使用過(guò)的空間中,這種算法當(dāng)控件存活的對(duì)象比較少時(shí),極為高效,但是帶來(lái)的成本是需要一塊內(nèi)存交換空間用于進(jìn)行對(duì)象的移動(dòng)

2.2.3 標(biāo)記整理算法
標(biāo)記-整理算法采用標(biāo)記-清除算法一樣的方式進(jìn)行對(duì)象的標(biāo)記,但在清除時(shí)不同,在回收不存活的對(duì)象占用的空間后,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針。標(biāo)記-整理算法是在標(biāo)記清除算法的基礎(chǔ)上,又進(jìn)行了對(duì)象的移動(dòng),因此成本更高,但是卻解決了內(nèi)存碎片的問(wèn)題。

3. JVM 常見(jiàn)垃圾回收器
為了達(dá)到最優(yōu)效果,JVM分別針對(duì)新生代和舊生代實(shí)現(xiàn)了不同的垃圾回收器。如圖:

3.1 串行回收器(Serial)
Serial收集器是歷史最悠久的一個(gè)回收器,JDK1.3之前廣泛使用這個(gè)收集器,目前也是ClientVM下 ServerVM 4核4GB以下機(jī)器的默認(rèn)垃圾回收器。串行收集器并不是只能使用一個(gè)CPU進(jìn)行收集,而是當(dāng)JVM需要進(jìn)行垃圾回收的時(shí)候,需要中斷所有的用戶線程,知道它回收結(jié)束為止,因此又號(hào)稱“Stop The World”的垃圾回收器。
3.2 ParNew回收器
ParNew收集器其實(shí)就是多線程版本的Serial收集器,同樣有
Stop The World的問(wèn)題,他是多CPU模式下的首選回收器(該回收器在單CPU的環(huán)境下回收效率遠(yuǎn)遠(yuǎn)低于Serial收集器,所以一定要注意場(chǎng)景哦),也是Server模式下的默認(rèn)收集器。
3.3 ParallelScavenge
ParallelScavenge又被稱為是吞吐量?jī)?yōu)先的收集器。
3.4 SerialOld
SerialOld是舊生代Client模式下的默認(rèn)收集器,單線程執(zhí)行;在JDK1.6之前也是ParallelScvenge回收新生代模式下舊生代的默認(rèn)收集器,同時(shí)也是并發(fā)收集器CMS回收失敗后的備用收集器。
3.5 ParallelOld
ParallelOld是老生代并行收集器的一種,使用標(biāo)記整理算法、是老生代吞吐量?jī)?yōu)先的一個(gè)收集器。這個(gè)收集器是JDK1.6之后剛引入的一款收集器,早期沒(méi)有ParallelOld之前,吞吐量?jī)?yōu)先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量?jī)?yōu)先的性能,自從JDK1.6之后,才能真正做到較高效率的吞吐量?jī)?yōu)先。
3.6 CMS
CMS又稱響應(yīng)時(shí)間優(yōu)先(最短回收停頓)的回收器,使用并發(fā)模式回收垃圾,使用標(biāo)記-清除算法,CMS對(duì)CPU是非常敏感的,它的回收線程數(shù)=(CPU+3)/4,因此當(dāng)CPU是2核的實(shí)惠,回收線程將占用的CPU資源的50%,而當(dāng)CPU核心數(shù)為4時(shí)僅占用25%。