深入Java底層:GC

概述

下文主要分為以下幾個大模塊進行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為起點進

截屏2019-12-26上午10.47.34.png

行引用鏈搜索,如果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ù)
  1. -XX:SurvivorRatio:新生代中Eden區(qū)Survivor區(qū)大小的比值,默認8:1
  2. -XX:NewRatio:老年代和年輕代的大小比值
  3. -XX:MaxTenuringThreshold:對象從年輕代晉升到老年代需要經歷的GC次數(shù)的最大閾值

3.年輕代垃圾收集器

Serial收集器

使用-XX:+UseSerialGC設置年輕代使用此垃圾收集器,采用復制算法。單線程收集,在進行垃圾收集時必須暫停其他所有工作的線程,簡單高效,JVM的Client模式下的默認垃圾收集器


Serial收集器
ParNew收集器

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


ParNew收集器
Parallel Scavenge收集器

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


Parallel Scavenge收集器

如果對其垃圾收集器的工作原理不太理解,常常使用此垃圾收集器配合-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ā)收集器)


CMS收集器
G1收集器

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

各個收集器之間的可兼容關系
各個收集器之間的可兼容關系

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容