JVM的垃圾回收算法
JVM的垃圾回收算法
一,如何判斷對象已經(jīng)消亡
1,引用計(jì)數(shù)算法
一個(gè)對象如果沒有任何引用指向它,就可認(rèn)為該對象已經(jīng)”消亡“,這種方法有個(gè)缺點(diǎn)就是無法檢測到引用環(huán)的存在。
算法特點(diǎn)
1. 需要單獨(dú)的字段存儲(chǔ)計(jì)數(shù)器,增加了存儲(chǔ)空間的開銷;
2. 每次賦值都需要更新計(jì)數(shù)器,增加了時(shí)間開銷;
3. 垃圾對象便于辨識,只要計(jì)數(shù)器為0,就可作為垃圾回收;
4. 及時(shí)回收垃圾,沒有延遲性;
5. 不能解決循環(huán)引用的問題;
2,根搜索算法
Java使用根搜索算法回收垃圾,該算法的基本原理:定義一系列名為GC Roots的對象作為起點(diǎn),從起點(diǎn)向下搜索,搜索所走過的路徑稱為引用鏈。
當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連,則說明該對象不可用,這時(shí)Java虛擬機(jī)可以對這些對象進(jìn)行回收。
Java虛擬機(jī)將以下對象定義為 GC Roots :
1), Java虛擬機(jī)棧中引用的對象:比如方法里面定義這種局部變量 User user= new User();
2),靜態(tài)屬性引用的對象:比如 private static User user = new User();
3),常量引用的對象:比如 private static final User user = new User();
4),本地方法棧中引用的對象
二,常用的垃圾回收算法
1,標(biāo)記-清除算法。
該算法包含標(biāo)記和清楚兩個(gè)階段:
首先是標(biāo)記處所有需要回收的垃圾對象,標(biāo)記完成之后統(tǒng)一進(jìn)行回收處理。
該算法主要不足:
A),一個(gè)是效率問題。標(biāo)記和清楚兩個(gè)過程的效率都不高。
B),一個(gè)是空間的問題。標(biāo)記清楚之后會(huì)導(dǎo)致大量不連續(xù)的內(nèi)存碎片。空間碎片過多會(huì)導(dǎo)致在程序運(yùn)行過程中需要分片大量內(nèi)存的時(shí)候無法找到滿足連續(xù)內(nèi)存而不得不提前出發(fā)另一次垃圾回收動(dòng)作。標(biāo)記-清除算法的執(zhí)行過程如下圖:

2,復(fù)制算法
此算法把內(nèi)存劃分為相等大小的兩個(gè)區(qū)域,每一只使用其中一個(gè),回收過程中將存活的對象全部復(fù)制到另一個(gè)區(qū)域中,清空原區(qū)域。在年輕代中eden區(qū)和兩個(gè)survivor區(qū)就是使用了此種算法。這種算法只復(fù)制存活的對象,成本較低,而且不會(huì)出現(xiàn)內(nèi)存碎片問題,缺點(diǎn)是需要2倍的內(nèi)存空間。

3,標(biāo)記整理算法
復(fù)制收集算法在對象存活率較高的時(shí)候就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變的很低。更關(guān)鍵是,如果你不想浪費(fèi)一半內(nèi)存空間,就需要有額外的內(nèi)存空間進(jìn)行分配擔(dān)保,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以老年代不采用這種算法。
根據(jù)老年代的特點(diǎn),有人提出了另外一種標(biāo)記-整理算法,標(biāo)記過程仍然與標(biāo)記清除算法一樣,然后直接清理掉端邊界以外的內(nèi)存,”標(biāo)記-整理”算法的示意圖如下:

4,分代回收算法
當(dāng)前商業(yè)虛擬機(jī)的垃圾回收器都采用分代收集算法,這種算法并沒有什么新的思想,只是根據(jù)對象存活周期的不同將內(nèi)存花費(fèi)為幾塊。一般是把java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āT谛律校看卫占瘯r(shí)都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對象的復(fù)制陳本就可以完成收集。而老年代中因?yàn)閷ο蟠婊盥矢撸瑳]有額外空間對它進(jìn)行分配擔(dān)保,就必須使用標(biāo)記清除或者標(biāo)記整理算法來進(jìn)行垃圾回收。
三,內(nèi)存分配和回收的策略
目前JVM分代主要是分三個(gè)年代:
新生代:所有新創(chuàng)建的對象都首先在新生代進(jìn)行內(nèi)存分配。新生代具體又分為3個(gè)區(qū),一個(gè)Eden區(qū)、一個(gè)From Survivor區(qū)和一個(gè)To Sruvivor區(qū)。大部分對象都被分配在Eden區(qū),當(dāng)Eden區(qū)滿時(shí),還存活的對象將被復(fù)制到From Survivor區(qū),當(dāng)From Survivor區(qū)滿時(shí),此區(qū)還存活的對象將被復(fù)制到To Survivor區(qū)。最后,當(dāng)To Survivor區(qū)也滿時(shí),這時(shí)從From Survivor區(qū)復(fù)制過來并且還存活的對象將被復(fù)制到老年代。
老年代:在年輕代中經(jīng)歷了N次(一般是15次)GC后依然存活的對象,就會(huì)被放到老年代當(dāng)中。因此,可以認(rèn)為老年代是存放一些生命周期較長的對象。
持久代:用于存放靜態(tài)文件,如Java類等。

GC的種類:
新生代GC(Minor GC):
指的是發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)?/span>Java對象大多都具備朝生夕滅的特性,所以MinorGC非常頻繁,一般回收速度也比較快。
老年代GC(MajorGC/FullGC):
指發(fā)生在老年代的GC,出現(xiàn)了MajorGC,經(jīng)常會(huì)伴隨至少一次的MinorGC(但非絕對,在Parallel Scavenger 收集器的收集策略里就有直接進(jìn)行MajorGC的策略選擇過程)。
MajorGC的速度一般會(huì)比MinorGC慢10倍以上。
對象的分配策略:
大多數(shù)情況下,對象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次GC。
大對象直接進(jìn)入老年代。所謂的大對象是指,需要大量連續(xù)內(nèi)存空間的Java對象,最經(jīng)典的大對象就是那種很長字符串以及數(shù)組。
長期存活的對象將進(jìn)入老年代。虛擬機(jī)為了區(qū)別對象是應(yīng)該放在老年代還是新生代,給每個(gè)對象定義了一個(gè)對象年齡計(jì)數(shù)器。如果對象在Eden出生并經(jīng)過第一次MinorGC后任然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間,并且將年齡設(shè)置為1.對象每在Survivor區(qū)中熬過一次MinorGC,年齡就會(huì)增加1歲。當(dāng)他的年齡超過一定程度(默認(rèn)是15歲),就會(huì)被晉升到老年代中。對象晉升到老年代的閾值可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置。
動(dòng)態(tài)對象年齡綁定:
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對象的年齡必須達(dá)到了MaxTenuringThreshold才晉升老年代,如果在Survivor空間中相同年齡所有對象大小綜合大于Survivor空間的一半,年齡大于或者等于該年齡的對象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。
空間分配擔(dān)保:
在發(fā)生MinorGC之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象的總空間,如果這個(gè)條件成立,那么MinorGC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于則嘗試進(jìn)行一次MinorGC,盡管這次MinorGC是有風(fēng)險(xiǎn)的;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那么也要改為進(jìn)行一次FullGC。
風(fēng)險(xiǎn)其實(shí)就是指:老年代的空間不一定能容納青年代所有存活的對象,一旦不能容納,那就還需要進(jìn)行一次Full GC。
四,垃圾回收器

