天天看點

JVM-GC算法與種類

一、基礎概念

根節點:

1、棧中引用的對象;

2、方法區中靜态成員或者常量引用的對象(全局對象);

3、JNI方法棧中引用對象;

可達性分析(Reachability Analysis):從根節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到根節點有引用鍊相連時,則證明此對象是可達對象;

可複活對象:在finalize()方法中複活的對象;

不可達對象:既沒有引用鍊又不可複活的對象,稱之為不可達對象。不可達對象是要被GC回收的對象;

Minor GC:從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC;

Major GC: 清理永久代垃圾;

Full GC: 是清理整個堆空間—包括年輕代、永久代、老年代

二、GC的概念

GC:Garbage Collection 垃圾收集,垃圾是指系統在運作期間産生的一些無用對象,若這些對象長時間不被釋放,則會導緻系統記憶體溢出。

java中,GC的對象時堆空間和永久區。

三、GC的算法

1、引用計數法

引用計數法是老的垃圾回收算法,它的實作很簡單,在每一個對象上标記一個引用計數器,假設有一個對象A,隻要有任何一個對象引用了A,則A的引用計數器就會+1,當引用失效時,引用計數器就會-1,當引用計數器為0時,則表示A對象不被引用了,将會被垃圾回收。

例如:

JVM-GC算法與種類

當對象C與對象B斷開引用時,C的引用計數器既為0,C也就會被垃圾回收

引用計數法的問題

1、引用和去引用伴随着引用計數器的加法和減法,影響性能;

2、很難處理循環應用

JVM-GC算法與種類

當跟對象與其他對象斷開引用時,那麼其他對象對于跟對象來說是不可達的對象了,但是其他三個對象在循環引用,是以引用計數器不會為0,則不會被當做垃圾回收。

2、标記-清除算法

标記-清除算法是現代垃圾回收算法的思想基礎,它将垃圾回收分為兩個階段,即标記階段和清除階段。

在标記階段,首先通過根節點,标記所有從根節點開始的可達對象,是以未被标記的即是未被引用的垃圾對象,在清除階段就清除這些未被标記的對象。

缺點:

1、暫停整個應用;

2、會産生記憶體碎片;

JVM-GC算法與種類

3、标記-壓縮算法

标記-壓縮算法适合用于存活對象較多的場合,如老年代。它在标記-清除算法的基礎上做了一些優化。和标記-清除算法一樣,标記-壓縮算法也首先需要從根節點開始,對所有可達對象做一次标記。但之後,它并不簡單的清理未标記的對象,而是将所有的存活對象壓縮到記憶體的一端。之後,清理邊界外所有的空間。

JVM-GC算法與種類

4、複制算法

複制算法是将原有的記憶體空間非為兩塊,每次隻使用其中一塊,在垃圾回收時,将正在使用的存活對象,複制到未被使用的記憶體塊中去,然後将正在使用的記憶體塊中的對象全部清除,交換兩個記憶體塊的角色,完成垃圾回收。

與标記-清除算法相比,複制算法是一種相對高效的回收方法

不适用于存活對象較多的場合 如老年代

缺點:

1、暫停整個應用;

2、需要2倍的記憶體空間;

JVM-GC算法與種類
JVM-GC算法與種類

老年代:存放從年輕代(young)複制過來的對象。生命周期較長的對象,歸入到tenured generation。一般是經過多次minor gc,還依舊存活的對象,将移入到tenured generation。(當然,在minor gc中如果存活的對象的超過survivor的容量,放不下的對象會直接移入到tenured generation)。

持久代(perm):用于存放靜态檔案,如Java類、方法等。持久代對垃圾回收沒有顯著的影響,但是有些應用可能動态生成或者調用一些class。

四、分代思想

依據對象的存活周期進行分類,短命對象歸為新生代,長命對象歸為老年代。每次GC存活的對象,年齡上都+1。

根據不同代的特點,選取合适的收集算法

少量對象存活,适合複制算法

大量對象存活,适合标記清理或者标記壓縮

五、JVM的垃圾回收過程

首先從跟對象開始進行可達性分析,判斷哪些是不可達對象。

對于不可達對象,判斷是否需要執行其finalize方法,如果對象沒有覆寫finalize方法或已經執行過finalize方法則視為不需要執行,進行回收;如果需要,則把對象加入F-Queue隊列。

對于F-Queue隊列裡的對象,稍後虛拟機會自動建立一個低優先級的線程去觸發其finalize方法,但不會等待這個方法傳回。

如果在finalize方法的執行過程中,對象重新被引用,那麼進行第二次标記時将被移出F-Queue,在finalize方法執行完成後,對象仍然沒有被引用,則進行回收。

對于被移出F-Queue的對象,如果它下一次面臨回收時,将不會再執行其finalize方法。

finalize方法隻執行一次。

六、導緻Minor GC的情況:

當Eden區域配置設定記憶體時,發現空間不足,JVM就會觸發Minor GC,程式中System.gc()也可以來觸發。

七、導緻Full GC的情況:

除直接調用System.gc外,觸發Full GC執行的情況有如下四種:

1. 老年代空間不足

老年代空間隻有在新生代對象轉入及建立為大對象、大數組時才會出現不足的現象,當執行Full GC後空間仍然不足,則抛出如下錯誤:java.lang.OutOfMemoryError: Java heap space

為避免以上兩種狀況引起的FullGC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。

2. 持久代空間滿

PermanetGeneration中存放的為一些class的資訊等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會抛出如下錯誤資訊:java.lang.OutOfMemoryError: PermGen space

為避免Perm Gen占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。

3. CMS GC時出現promotion failed和concurrent mode failure

對于采用CMS進行舊生代GC的程式而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。

promotionfailed是在進行Minor GC時,survivor space放不下、對象隻能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入老年代,而此時老年代空間不足造成的。

應對措施為:增大survivorspace、老年代空間或調低觸發并發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由于JDK的bug29導緻CMS在remark完畢後很久才觸發sweeping動作。對于這種狀況,可通過設定-XX:CMSMaxAbortablePrecleanTime=5(機關為ms)來避免。

4. 統計得到的Minor GC晉升到老年代的平均大小大于舊生代的剩餘空間

這是一個較為複雜的觸發情況,Hotspot為了避免由于新生代對象晉升到老年代導緻老年代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到老年代的平均大小大于老年代的剩餘空間,那麼就直接觸發Full GC。

例如程式第一次觸發MinorGC後,有6MB的對象晉升到老年代,那麼當下一次Minor GC發生時,首先檢查老年代的剩餘空間是否大于6MB,如果小于6MB,則執行Full GC。

當新生代采用PSGC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時老年代的剩餘空間是否大于6MB,如小于,則觸發對老年代的回收。。

八、關于finalize方法的問題

finalize方法使得GC過程做了更多的事情,增加的GC的負擔。

如果某個對象的finalize方法運作時間過長,它會使得其他對象的finalize方法被延遲執行。

finalize方法中如果建立了strong reference引用了其他對象,這會阻止此對象被GC。

finalize方法有可能以不可确定的順序執行(也就是說要在安全性要求嚴格的場景中盡量避免使用finalize方法)。

不確定finalize方法會被及時調用,也許程式都退出了,但是finalize方法還沒被調用。

可以使用try-catch-finally來替代它

九、Stop-The-World

Java中一種全局暫停的現象

全局停頓,所有Java代碼停止,native代碼可以執行,但不能和JVM互動

引起原因

1、多半由于GC引起

2、Dump線程

3、死鎖檢查

4、堆Dump

GC時為什麼會有全局停頓?

類比在聚會時打掃房間,聚會時很亂,又有新的垃圾産生,房間永遠打掃不幹淨,隻有讓大家停止活動了,才能将房間打掃幹淨。

危害

長時間服務停止,沒有響應

遇到HA系統,可能引起主備切換,嚴重危害生産環境。

參考文章:

http://itindex.net/detail/47030-cms-gc-問題