JVM之垃圾回收

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


JVM虛擬機(jī)架構(gòu)

上面是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)而得到的。? ?

標(biāo)記-清除

它的主要缺點(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)存空間一次清理掉。

復(fù)制(Copying)的收集算法

? ? ? ? 該算法使得每次都是對其中的一塊進(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)存。

標(biāo)記-整理


“分代收集”(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收集過程

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ù)說是相對較好的,先留個坑之后填上。


GC收集器組合

有關(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毫秒。停頓時間的確在下降,但吞吐量也降下來了。

最后附上該篇文章大概的腦圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡欲明心閱讀 90,374評論 17 311
  • 在 JVM 調(diào)優(yōu)中一個離不開的重點(diǎn)是垃圾回收,當(dāng)垃圾回收成為系統(tǒng)達(dá)到更高并發(fā)量的瓶頸時,我們就需要對 JVM 中 ...
    Little丶Jerry閱讀 221評論 0 0
  • 作者:一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-12】 更新日志 日期更新內(nèi)容備注 2017-11-12新建文章初版 ...
    beneke閱讀 2,329評論 0 7
  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,157評論 2 31
  • 下雨時 最能讓思緒翻滾 靜靜看著窗外的雨絲 拍打著坑洼的路面 路上是稀疏而匆匆的行人 他們逆雨前行 不知道他們?nèi)ネ?..
    鄉(xiāng)下讀書人閱讀 211評論 0 0

友情鏈接更多精彩內(nèi)容