JVM 常用垃圾收集算法和垃圾收集器

垃圾收集算法

標記-清除算法(Mark-Sweep)

算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成之后統(tǒng)一回收掉所有被標記的對象。

缺點
1、 標記和清除效率都不高
2、 清除后會產(chǎn)生大量的不連續(xù)內(nèi)存碎片,碎片太多,導(dǎo)致程序需要為大對象分配內(nèi)存時無法找到足夠的連續(xù)內(nèi)存不得不觸發(fā)另一次垃圾收集

標記-整理算法

標記整理在標記清除的基礎(chǔ)上做了改進,第一階段標記階段,同標記清除的標記,標記出所有需要回收的對象,但是第二階段整理階段是: 在標記完成后不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,在移動過程中清理掉可回收的對象。

相比于標記清除,優(yōu)點是內(nèi)存整理后不會產(chǎn)生大量不連續(xù)的內(nèi)存碎片

對象存活率高的情況下使用標記-整理算法效率會大大提高。

復(fù)制算法

為了解決 Mark-Sweep 算法內(nèi)存碎片化的缺陷而被提出的算法。按內(nèi)存容量將內(nèi)存劃分為等大小
的兩塊。每次只使用其中一塊,當這一塊內(nèi)存滿后將尚存活的對象復(fù)制到另一塊上去,把已使用
的內(nèi)存清掉

優(yōu)點:
實現(xiàn)簡單,內(nèi)存效率高,不易產(chǎn)生空間碎片

缺點:
很明顯,需要預(yù)留一般的內(nèi)存作為復(fù)制對象的空間,導(dǎo)致可用內(nèi)存只有原來的一半
對象存活率高的情況下就要執(zhí)行較多的復(fù)制操作,效率將會變低

分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根據(jù)對象存活的不同生命周期將內(nèi)存
劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young
Generation)。老生代的特點是每次垃圾回收時只有少量對象需要被回收,新生代的特點是每次垃
圾回收時都有大量垃圾需要被回收,因此可以根據(jù)不同區(qū)域選擇不同的算法。

新生代的復(fù)制算法

Eden/S0/S1默認空間比例Eden:S0:S1為8:1:1,有效內(nèi)存(即可分配新生對象的內(nèi)存[Eden和Survivor1])是總內(nèi)存的90%。Eden/S0/S1三個空間的比例為8:1:1,則可能會出現(xiàn)Eden+S0中存活對象超過了總空間的10%(S1、S0的空間都是總空間的10%),在這種情況下,新生代GC會將存活周期長的對象直接放入老生代,而無需達到我們設(shè)置的閾值(轉(zhuǎn)入老生代的存活次數(shù),-XX:MaxTenuringThreshold)

算法過程

Eden+S0可分配新生對象;
    對Eden+S0進行垃圾收集,存活對象復(fù)制到S1。清理Eden+S0。一次新生代GC結(jié)束。
Eden+S1可分配新生對象;
    對Eden+S1進行垃圾收集,存活對象復(fù)制到S0。清理Eden+S1。二次新生代GC結(jié)束。
循環(huán)1。

HotSpot實現(xiàn)復(fù)制算法的流程:

1、當Eden區(qū)滿的時候,會觸發(fā)第一次Minor gc,把還活著的對象拷貝到Survivor From區(qū);
2、當Eden區(qū)再次觸發(fā)Minor gc的時候,會掃描Eden和From區(qū)域,對兩個區(qū)域進行垃圾回收,經(jīng)過這次回收后還存活的對象,直接賦值到To區(qū)域(如果出現(xiàn)To區(qū)域剩余空間不夠復(fù)制原有存活對象的情況,則通過老年代進行內(nèi)存擔保,把這些對象直接復(fù)制到老年代,不用等待MaxTenuringThreshold),并將Eden和From區(qū)域清空。
3、當后續(xù)Eden又發(fā)生Minor gc的時候,會對Eden和To區(qū)域進行垃圾回收,存活的對象復(fù)制到From區(qū)域,并將Eden和To區(qū)域清空。
4、部分對象會在From和To區(qū)域中復(fù)制來復(fù)制去,如此交換15次(由JVM參數(shù)MaxTenuringThreshold決定,這個參數(shù)默認是15),最終還是存活,就存入老年代。

Stop-the-world

當Stop-the-world發(fā)生時,除了GC所需的線程以外,所有線程都處于等待狀態(tài),直到GC任務(wù)完成。GC優(yōu)化很多時候就是指減少Stop-the-world發(fā)生的時間。
意味著 JVM 因為要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行
MinGC\MajorGC都屬于Stop-the-world, 那為什么MajorGC耗時較長呢?因為OldGen包含了大量存貨下來的對象。

MinorGC

新生代GC Young GC
清理年輕代(Eden和Survivor區(qū))
從年輕代空間(主要是 Eden區(qū))回收內(nèi)存被稱為 Minor GC。
新生代(主要指Eden區(qū))的Minor GC 比較常見,各個收集器均支持。

觸發(fā)條件:
eden區(qū)滿時,觸發(fā)MinorGC。即申請一個對象時,發(fā)現(xiàn)eden區(qū)不夠用,則觸發(fā)一次MinorGC。
當 JVM 無法為一個新的對象分配空間時會觸發(fā) Minor GC,比如當 Eden 區(qū)滿了。所以分配率越高,越頻繁執(zhí)行 Minor GC。
Survivor滿不會觸發(fā)Minor GC

新生代分為三個區(qū)域,eden space, from space, to space。默認比例是8:1:1。在MinorGC時,會把存活的對象復(fù)制到to space區(qū)域,如果to space區(qū)域不夠,則利用擔保機制進入老年代區(qū)域。

所有的 Minor GC 都會觸發(fā)“全世界的暫停(stop-the-world)”,停止應(yīng)用程序的線程。對于大部分應(yīng)用程序,停頓導(dǎo)致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區(qū)中的對象都能被認為是垃圾,永遠也不會被復(fù)制到 Survivor 區(qū)或者老年代空間。如果正好相反,Eden 區(qū)大部分新生對象不符合 GC 條件,Minor GC 執(zhí)行時暫停的時間將會長很多。

執(zhí)行 Minor GC 操作時,不會影響到永久代。從永久代到年輕代的引用被當成 GC roots,從年輕代到永久代的引用在標記階段被直接忽略掉。

MajorGC

老年代GC Old GC
出現(xiàn)Major GC通常會出現(xiàn)至少一次Minor GC。
清理老年代
目前,只有CMS收集器會有單獨收集老年代的行為。

FullGC

針對整個新生代、老年代、元空間(metaspace, java8以上版本取代永久代)、堆外內(nèi)存的全局范圍的GC,
注意 FullGC 不等于Major GC , 也不等于 Minor GC + Major GC ,取決于什么樣的垃圾收集器組合

觸發(fā)條件

1、當年老代滿時會引發(fā)Full GC,F(xiàn)ull GC將會同時回收新生代、年老代 ;
2、當永久代滿時也會引發(fā)Full GC,會導(dǎo)致Class、Method元信息的卸載 。
3、調(diào)用System.gc時,系統(tǒng)建議執(zhí)行Full GC,但是不一定會執(zhí)行
4、通過 Minor GC 后進入老年代的空間大于老年代的可用內(nèi)存
5、由Eden區(qū)、survivor space1(From Space)區(qū)向survivor space2(To Space)區(qū)復(fù)制時,對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小 

注意,很多時候,Major GC 會和Full GC混淆使用,需要具體分辨是老年代的回收還是整堆回收。

Mixed GC

指目標是收集整個新生代以及部分老年代的垃圾收集。
目前只有G1收集器會有這種行為

類卸載的條件

1. 該類所有的實例已經(jīng)被回收
2. 加載該類的ClassLoder已經(jīng)被回收
3. 該類對應(yīng)的java.lang.Class對象沒有任何對方被引用

垃圾收集器:

新生代垃圾收集器

Serial

單線程 STW 回收(暫停所有用戶線程)  Client 模式下的虛擬機   新生代   復(fù)制算法

ParNew

多線程 STW 回收(暫停所有用戶線程)  Server 模式下的虛擬機   新生代   復(fù)制算法

Parallel Scavenge

多線程 STW 回收(暫停所有用戶線程)  Server 模式下的虛擬機   新生代   復(fù)制算法
目標是達到一個可控的吞吐量(吞吐量=運行用戶代碼時間/(運行用戶代碼的時間+垃圾收集的時間))  吞吐量優(yōu)先垃圾器
自適應(yīng)的調(diào)節(jié)策略(自動根據(jù)系統(tǒng)運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整(新生代大小、Eden與Survivor的比例,晉升老年代對象的年齡等)以提供合適的停頓時間或者最大吞吐量)

老年代垃圾收集器

Serial Old

單線程 STW 回收(暫停所有用戶線程)  Client 模式下的虛擬機   老年代   標記整理

Parallel Old

是Parallel Scavenge 收集器的老年代版本,
多線程 STW 回收(暫停所有用戶線程)  Server 模式下的虛擬機   老年代   標記整理

CMS

Concurrent Mark Sweep
一種獲取最短回收停頓時間為目標的收集器
多線程    老年代   標記清除   CUP敏感  默認啟動回收線程數(shù)= (cpu+3)/4  無法清除浮動垃圾

1、初始標記 STW  僅僅標記GC Roots能直接關(guān)聯(lián)的對象,速度很快
2、并發(fā)標記      用戶線程和GC線程并行執(zhí)行
3、重新標記 STW
4、并發(fā)清除      用戶線程和GC線程并行執(zhí)行

G1

1、初始標記(Initial Marking)
僅僅只是標記一下GC Roots能直接關(guān)聯(lián)到的對象,并且修改TAMS 指針的值,讓下一階段用戶線程并發(fā)運行時,
能正確地在可用的Region中分配新對象。這個階段需要 停頓線程,但耗時很短,
而且是借用進行Minor GC的時候同步完成的,所以G1收集器在這個階段實際 并沒有額外的停頓。 
2、并發(fā)標記(Concurrent Marking)
從GC Root開始對堆中對象進行可達性分析,遞歸掃描整個堆 里的對象圖,找出要回收的對象,這階段耗時較長,
但可與用戶程序并發(fā)執(zhí)行。當對象圖掃描完成以 后,還要重新處理SATB記錄下的在并發(fā)時有引用變動的對象。 ·
3、 最終標記(Final Marking):
對用戶線程做另一個短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留 下來的最后那少量的SATB記錄。 
4、篩選回收(Live Data Counting and Evacuation)
負責更新Region的統(tǒng)計數(shù)據(jù),對各個Region的回 收價值和成本進行排序,
根據(jù)用戶所期望的停頓時間來制定回收計劃,可以自由選擇任意多個Region 構(gòu)成回收集,
然后把決定回收的那一部分Region的存活對象復(fù)制到空的Region中,
再清理掉整個舊 Region的全部空間。這里的操作涉及存活對象的移動,是必須暫停用戶線程,由多條收集器線程并行 完成的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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