概述
下文主要分為以下幾個大模塊進行JVM的GC解讀:
- 垃圾回收之標記算法
- 垃圾回收之回收算法
- 堆內存年輕代垃圾收集器
- 堆內存老年代垃圾收集器
1.垃圾回收之標記算法
既然是垃圾回收,首先就是要判斷哪些對象實例是垃圾,可以被回收,標記算法的用處就在于此
引用計數(shù)法
Java中通過引用關聯(lián)對象,顯然可以通過引用計數(shù)的方式來判斷一個對象是否可以被回收。如果一個對象沒有和任何一個引用相關聯(lián),那這個對象就可以被回收。這種算法實現(xiàn)方式簡單,效率高,但是無法解決循環(huán)引用的問題,如下這段代碼:
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
class MyObject{
public Object object;
}
由于object1和object2相互引用對方,它們的引用計數(shù)永遠不為0,永遠不會被標記而回收
可達性分析法
Java中采取的就是可達性分析算法。此算法以GC Root為起點進

行引用鏈搜索,如果GC Root和目標對象之間沒有可達路徑,則此對象不可達,但是成為可被回收的對象需要經歷兩次可達性分析算法的標記分析,如果兩次都被標記不可達才會被作為”垃圾“。
常見的GC Root有哪些呢?
- 虛擬機棧中的引用對象
- 方法區(qū)中的常量引用對象
- 方法區(qū)中靜態(tài)屬性引用對象
- 本地方法棧棧中JNI的引用對象
- 活躍線程的引用對象
2.垃圾回收之回收算法
標記算法完成了”垃圾“的標記,下一步就是回收這些”垃圾“
標記-清除算法(mark-sweep)

標記-清除算法分為兩步驟,第一步就是利用標記算法找到”垃圾“,第二步清除可被回收的對象。此算法思路簡單易于實現(xiàn),但是從上圖中可以看出標記-清除算法比較容易導致內存碎片化,如果有較大的對象需要存儲時,可能無法找到足夠的內存空間存儲,從而又觸發(fā)一次新的垃圾回收動作
復制算法(copying)
為了解決標記清除算法導致的內存碎片化問題,就有了復制算法。
它把內存空間分為兩大塊,一塊為對象面,一塊為空閑面;新創(chuàng)建的對象都在對象面上,直到對象面內存空間滿了,把對象面里不是垃圾的存活對象全部復制到空閑面,然后把整個對象面內存空間全部清除,如下圖:

此算法雖然避免了內存碎片化,但是可使用的內存空間卻減少了一半,并且此算法效率和存貨對象的數(shù)目有很大關系,適用于存活對象數(shù)目較少的情況
標記-整理算法(mark-compact)
標記整理算法是標記清除算法的更近一步,在完成垃圾標記后,不是直接清除,而是把存活對象都移動至內存一端,按照地址順序排列,最后把末端地址后的內存空間全部清除,如下圖:

此算法避免了內存碎片化,也不需要把內存空間分為兩大塊,適用于存活對象較多的情況
分代收集算法(generational-collection)
此算法是目前大部分JVM采取的垃圾收集算法,效率最高。它可以理解為一套”組合拳“算法,JVM根據(jù)對象的生命周期將堆劃分為年輕代和老年代,新生代的特點就是每次垃圾回收有較多的垃圾需要回收,老年代的特點就是每次垃圾回收有較少的垃圾需要回收。
因此對于年輕代的垃圾回收一般采用復制算法,因為年輕代的存活對象較少,反之老年代一般采用標記整理算法,因為老年代的存活對象較多。
年輕代單獨的垃圾回收被稱為Minor GC,年輕代的內存劃分其實并不像復制算里面描述的分為1:1,而是分為一個較大的Eden區(qū)和兩個較小的Survivor區(qū),大致空間大小劃分如下:

使用新代時,只使用Eden區(qū)和其中一個Survivor區(qū),另一個Survivor區(qū)空閑。當進行Minor GC時把Eden區(qū)和Survivor區(qū)中的存活對象復制到另一個空閑的Survivor區(qū),然后清除Eden區(qū)和使用過Survivor區(qū)。
整個堆的垃圾收集(包括年輕代和老年代)被稱為Full GC,也就是當觸發(fā)Full GC時同時也會觸發(fā)Minor GC,F(xiàn)ull GC的時間大概是Minor GC的十倍。觸發(fā)Full GC有以下幾個條件:
- 老年代空間不足
- 調用System.gc()
- 還有幾個我也不記得了
關于垃圾回收的幾個JVM性能調優(yōu)參數(shù)
- -XX:SurvivorRatio:新生代中Eden區(qū)Survivor區(qū)大小的比值,默認8:1
- -XX:NewRatio:老年代和年輕代的大小比值
- -XX:MaxTenuringThreshold:對象從年輕代晉升到老年代需要經歷的GC次數(shù)的最大閾值
3.年輕代垃圾收集器
Serial收集器
使用-XX:+UseSerialGC設置年輕代使用此垃圾收集器,采用復制算法。單線程收集,在進行垃圾收集時必須暫停其他所有工作的線程,簡單高效,JVM的Client模式下的默認垃圾收集器

ParNew收集器
使用-XX:+UseParNewGC設置年輕代使用此垃圾收集器,采用復制算法。多線程收集,進行垃圾收集時和Serial一樣暫停其他工作線程,其在多核cpu的情況下才能發(fā)揮其優(yōu)勢

Parallel Scavenge收集器
使用-XX:+UseParallelGC設置年輕代使用此垃圾收集器,采用復制算法。與ParNew一樣多線程收集,相比于前兩者收集器關注點為垃圾收集停頓時間,此垃圾收集器的關注點為吞吐量(用戶運行代碼的時間/用戶運行代碼的時間+垃圾收集時間),多核cpu才能發(fā)揮優(yōu)勢,JVM的Server模式下的默認垃圾收集器

如果對其垃圾收集器的工作原理不太理解,常常使用此垃圾收集器配合-XX:+UseAdaptiveSizePolicy自適應調節(jié)策略,把內存調優(yōu)任務交給JVM
4.老年代垃圾收集器
Serial Old收集器
使用-XX:+UseSerialOldGC設置老年代使用此垃圾收集器,采用標記-整理算法。其特點與Serial收集器的特點相同
Parallel Old收集器
使用-XX:+UseParallelGC設置老年代使用此垃圾收集器,采用標記-整理算法。其特點與Parallel Scavenge相同,多線程收集,吞吐量優(yōu)先,常與Parallel Scavenge配合使用達到高吞吐量的效果
CMS收集器
使用-XX:+UseConcMarkSweepGC設置老年代使用此垃圾收集器,采用標記-清除算法。CMS是一種以獲取最短停頓時間為目的的收集器,是垃圾收集和工作線程幾乎可以并行工作的收集器(并發(fā)收集器)

G1收集器
使用-XX:+UseG1GC設置老年代使用此垃圾收集器,采用復制+標記-整理算法。它是一款并行并發(fā)收集器,對整個堆空間進行垃圾收集,能建立可預測的停頓時間模型
各個收集器之間的可兼容關系

圖中上半部分為年輕代垃圾收集器,下半部分為老年代垃圾收集器,連線意味著可兼容