JVM提供了Serial收集器,Parallel收集器和CMS(Concurrent Mark-Sweep)并發標記清除收集器,但這些收集器有3個共同的問題:
a.老年代的回收要掃描整個Old老年代空間;
b.Young空間和Old空間是獨立且連續的內存塊,上面的垃圾回收器必須先決定Young和Old在虛擬空間的位置;
c.STW(stop the world)時間不可控的問題。
為解決上述問題,sun提出了G1回收器,JDK9的默認垃圾回收器是G1。JDK后續還有ZGC,Shenandoah和Epsilon(JDK 11)等。
關于以上的垃圾回收器可參考我之前的文章:http://www.lxweimin.com/p/e9c9f088c090
1.G1垃圾回收器
G1是面向多CPU、大容量內存的Server端的GC,它縮短了停頓時間,提高了吞吐量,進而替換掉CMS,彌補CMS的mark-sweep產生內存碎片的問題,故G1采用了并行且整體“mark+cpmpact”,局部(Region之間)copy的算法。
G1將整個heap分成大小相同的獨立分區(Region),使得Young和Old不再隔離,避免了在整個Heap中進行全區域的垃圾回收。G1跟蹤每個Region的垃圾回收價值的大小(即回收的空間大小和消耗時間的經驗值),并在后臺維護一個優先列表,每次根據允許的收集時間回收價值最大的Region(即Garbage First),這種方式使得G1能夠在有限的時間內獲取盡可能高的回收效率。
如果對象的引用都是在Region內部之間,那么對象回收就會很簡單。但是不同Region之間對象也是可能存在相互引用的,判斷一個Region對象是否被引用,是否需要掃描整個heap空間呢 ?
為解決這個問題,G1使用Remember Set的來解決的,即在每個Region中都有一個對應的Remember Set來來記錄Region對象之間以及其他收集器的Young和Old之間的引用。當JVM發現有對Reference類型數據進行寫操作時,會產生一個Writer Barried暫時中斷寫操作,檢查Reference引用的對象是否在不同的Region之間,如果是,則通過CardTable將相關引用記錄保存到被引用對象所屬的Region的Remember Set中,當進行垃圾回收時,在GC的根節點的枚舉范圍中加入Remember Set,這樣避免了全heap掃描,且避免了遺漏。
G1回收過程
如上圖,G1的垃圾回收流程和CMS很相似,具體過程如下:
1.初始標記
該階段僅標記GC根節點能夠直接關聯的對象(需要Stop-The-World,但耗時很短);
2.并發標記
從GC的根節點對heap中的對象進行可達性分析,標記出存活的對象。并發標記耗時比較長,但是可以和用戶程序并發執行。
3.最終標記
最終標記是為了修正并發標記期間用戶程序運行而導致標記變動的那部分標記記錄。JVM將并發標記這段時間用戶程序對heap中對象的修改記錄保存到Remember Set Logs中,在最終標記階段,JVM將Remember Set Logs同步到Remember Set中(需要停頓線程,但是可以并發執行)。
4.篩選回收
G1首先對各個Region的回收價值和成本進行排序,根據用戶期望的停頓時間來制定回收計劃,即根據可停頓時間長短來回決定收多少Region。此時可以采用可用戶程序并行方式,但是Stop-The-World可以極大提高回收效率。