? ? ? ? JVM是每個JAVA開發(fā)都會聽說了解過的東西,相關(guān)知識有一部分也是需要深入了解的,去年剛好有機(jī)會一次團(tuán)隊(duì)內(nèi)部技術(shù)分享,就做了這方面的一些準(zhǔn)備,簡單了解了JVM的垃圾回收機(jī)制。

上面是JVM架構(gòu),簡單說說數(shù)據(jù)區(qū)每一部分的作用:
Java堆(Heap),是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。
方法區(qū)(Method Area),與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
程序計(jì)數(shù)器(Program Counter Register),程序計(jì)數(shù)器(Program CounterRegister)是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里(僅是概念模型,各種虛擬機(jī)可能會通過一些更高效的方式去實(shí)現(xiàn)),字節(jié)碼解釋器工作時就是通過改變這個計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計(jì)數(shù)器來完成。
JVM棧(JVM Stacks),與程序計(jì)數(shù)器一樣,Java虛擬機(jī)棧(Java Virtual MachineStacks)也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(StackFrame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。
本篇主要所提及的垃圾回收機(jī)制,主要是針對JAVA堆的。
在討論垃圾回收機(jī)制之前,需要先知道JVM會回收哪些對象?
簡單來說,會被GC掉的對象是: 不是GC roots并且沒有被GC roots引用的對象。
這里不討論哪些對象、引用可以成為GC roots,如感興趣可以自行深入了解一下。
GC 算法
標(biāo)記-清除算法
? ? 算法分為“標(biāo)記”和“清除”兩個階段:標(biāo)記階段:找到所有可訪問的對象,做個標(biāo)記 ,清除階段:遍歷堆,把未被標(biāo)記的對象回收。之所以說它是最基礎(chǔ)的收集算法,是因?yàn)楹罄m(xù)的收集算法都是基于這種思路并對其缺點(diǎn)進(jìn)行改進(jìn)而得到的。? ?

它的主要缺點(diǎn)有兩個:
? ? ? ? 一個是效率問題,標(biāo)記和清除過程的效率都不高;
? ? ? ? 另外一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致,當(dāng)程序在以后的運(yùn)行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
復(fù)制(Copying)的收集算法
? ? ? ? 它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。

? ? ? ? 該算法使得每次都是對其中的一塊進(jìn)行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為原來的一半,持續(xù)復(fù)制長生存期的對象則導(dǎo)致效率降低。并且該算法在對象存活率較高時就要執(zhí)行較多的復(fù)制操作,效率將會變低。更關(guān)鍵的是,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
標(biāo)記-整理算法
? ? ? ? 根據(jù)老年代的特點(diǎn),有人提出了另外一種“標(biāo)記-整理”(Mark-Compact)算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

“分代收集”(GenerationalCollection)算法
? ? ? ? 把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴?。在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因?yàn)閷ο蟠婊盥矢?、沒有額外空間對它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清除”或“標(biāo)記-整理”算法來進(jìn)行回收。
GC收集器

Serial收集器是一個新生代收集器,單線程執(zhí)行,使用復(fù)制算法。它在進(jìn)行垃圾收集時,必須暫停其他所有的工作線程(用戶線程)。是Jvm client模式下默認(rèn)的新生代收集器。對于限定單個CPU的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。

ParNew其實(shí)就是serial的多線程版本,ParNew在單線程的情況下甚至不如Serial,ParNew是除了serial之外唯一能和CMS配合的。

Parallel Scavenge收集器和ParNew收集器類似,是一個新生代收集器。使用復(fù)制算法的并行多線程收集器。
主要適應(yīng)主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)。比如需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶的體驗(yàn);而高吞吐量則可以最高效率地利用CPU時間,盡快地完成程序的運(yùn)算任務(wù)等。


CMS算法主要分為四個步驟:
a. 暫停所有應(yīng)用線程(stop-the-word)并且標(biāo)記能直接從roots節(jié)點(diǎn)到達(dá)的節(jié)點(diǎn),雖然會造成STW,由于只掃描全局變量、棧等根節(jié)點(diǎn)直接到達(dá)的對象,這一步會非???。
b.恢復(fù)應(yīng)用線程,并且開啟gc線程從第一個步驟中掃描到的節(jié)點(diǎn)開始進(jìn)行深度遍歷并標(biāo)記,這個過程GC Thread和應(yīng)用線程是同時進(jìn)行的,當(dāng)應(yīng)用進(jìn)程改變了某個對象的狀態(tài)時會把當(dāng)前對象所在的page標(biāo)記為dirty,new的object所在的page也標(biāo)記為dirty,深度遍歷完整個對象圖。
c.暫停所有應(yīng)用進(jìn)程并掃描roots以及標(biāo)記為dirty page所在的區(qū)域,這個步驟也會暫停所有應(yīng)用,由于只掃描roots以及dirty page,因此暫停時間比較短。
d.恢復(fù)應(yīng)用進(jìn)程并對內(nèi)存進(jìn)行回收。
圖中C雖然未被GC roots引用,但依舊被標(biāo)記出來。是因?yàn)椴幌M谶^程中再次造成dirty page。因此C對象會在下次GC時被回收
(此處page如何分頁,暫時沒有找到相關(guān)資料。)
總之CMS將stop-the-world的時間降到最低,能給電商網(wǎng)站用戶帶來最好的體驗(yàn)。? ? ? ? ? ? ?
盡管CMS的GC線程對CPU的占用率會比較高,但在多核的服務(wù)器上還是展現(xiàn)了優(yōu)越的特性,目前也被部署在國內(nèi)的各大電商網(wǎng)站上。
下面列出七個基本的收集器:
Serial(串行GC)標(biāo)記-復(fù)制
Serial Old(MSC)(串行GC)標(biāo)記-整理
ParNew(并行GC)標(biāo)記-復(fù)制
Parallel Scavenge(并行回收GC)標(biāo)記-復(fù)制
Parallel Old(并行GC)標(biāo)記-整理
CMS(并發(fā)GC)標(biāo)記-清除
G1(JDK1.7+) (該收集器沒有過多了解,性能據(jù)說是相對較好的,先留個坑之后填上。

有關(guān)GC的調(diào)優(yōu)
我沒有工程上的具體調(diào)優(yōu)經(jīng)驗(yàn),不過了解的是:GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的:系統(tǒng)把新生代調(diào)小一些,收集300MB新生代肯定比收集500MB快吧,這也直接導(dǎo)致垃圾收集發(fā)生得更頻繁一些,原來10秒收集一次、每次停頓100毫秒,現(xiàn)在變成5秒收集一次、每次停頓70毫秒。停頓時間的確在下降,但吞吐量也降下來了。
最后附上該篇文章大概的腦圖
