jvm垃圾收集算法

文章目錄
  1. 1. 判斷對象是否存活
    1. 1.1. 可達性分析算法
  2. 2. 垃圾收集算法
    1. 2.1. 標記-清除算法
    2. 2.2. 複製算法
    3. 2.3. 標記-複製算法
  3. 3. 垃圾收集器
    1. 3.1. CMS收集器
    2. 3.2. G1收集器
  4. 4. 內存分配與回收策略
    1. 4.1. 參考
    2. 4.2. 捐贈

判斷對象是否存活

可達性分析算法

通過一系列稱爲”GC Roots”的對象作爲起點,從這些節點開始向下搜索,搜索所有走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時(從GC Roots到此對象不可達),則證明此對象是不可用的。
可作爲GC Roots的對象包括:

  • 虛擬機棧中所引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI引用的對象

引用的分類

  • 強引用(Strong Reference): 在代碼中普遍存在的,類似”Object obj = new Object()”這類引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象
  • 軟引用(Sofe Reference): 有用但並非必須的對象,可用SoftReference類來實現軟引用,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存異常異常。
  • 弱引用(Weak Reference): 被弱引用關聯的對象只能生存到下一次垃圾收集發生之前,JDK提供了WeakReference類來實現弱引用
  • 虛引用(Phantom Reference):也稱爲幽靈引用或幻影引用,是最弱的一種引用關係,JDK提供了PhantomReference類來實現虛引用。

不要使用finalize()方法來挽救對象。

JVM判定無用的類的條件:

  • 該類的所有實例已經被回收,java堆中不存在該類的任何示例
  • 加載該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class對象沒有在任何地方被引用

垃圾收集算法

標記-清除算法

即先標記所有需要回收的對象,在標記完成後統一進行回收。該算法的兩個不足:一個是效率問題,標記和清除兩個過程效率不高;另一個是空間問題,標記清除後產生大量不連續的內存碎片,空間碎片太多導致爲較大對象分配內存時,找不到足夠大的連續內存。

複製算法

將可用內存按容量劃分爲大小相對的兩塊,每次只使用其中一塊,當這一塊內存使用完,將還存活的對象複製到另一塊,然後將已使用過的內存一次清理掉,很明顯這種方式雖然解決了內存碎片問題,但是可用內存縮小爲原來的一半,太浪費了。現在大部分的虛擬機都使用這種方式來回收新生代,即Eden區與兩個Survivor區,默認Eden:一個Survivor=8:1

標記-複製算法

標記過程如標記-清除算法一樣,後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。

垃圾收集器

這裏主要列舉現在常用的收集器

CMS收集器

現在常用的一種垃圾收集器,基於標記-清除算法,收集過程包含4個步驟:

  • 初始標記
  • 併發標記
  • 重新標記
  • 併發清除

其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記就是進行GC Roots Trancing的過程,而重新標記階段則是爲了修正併發標記期間因用戶程序繼續運行而導致標記產生變動那一部分對象的標記記錄,這個階段的停頓時間比初始標記稍長一些,但遠比並發標記時間短。
由於整個過程耗時最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,所以從整體上看,CMS收集器的內存回收過程是與用戶線程併發執行的。

CMS收集器的缺點:

  • 對CPU資源敏感 雖然不會導致用戶線程停頓,但會佔用一部分線程導致應用程序變慢,總吞吐量會降低。CMS默認啓動的回收線程數是(CPU數量+3)/4
  • 無法處理浮動垃圾 可能出現”Concurrent Mode Failure”失敗而導致另一次Full GC的產生。浮動垃圾是回收的過程與用戶線程並行時用戶線程產生的垃圾。
  • 產生內存碎片 使用標記-清除算法

G1收集器

一款面向服務端應用的比較新的垃圾收集器,具備以下特點:

  • 並行與併發 G1能夠充分利用多CPU、多核環境下的硬件優勢,使用多個CPU核心來縮短Stop The World的停頓時間
  • 分代收集
  • 空間整合 同時使用標記-整理與複製算法不會產生內存空間碎片
  • 可預測的停頓 能讓使用者指定在一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒

G1的垃圾收集步驟:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收

內存分配與回收策略

  1. 對象優先在Eden分配,當Eden沒有足夠的空間時,虛擬機發起一次Minor GC。可以通過JVM參數:-XX:+PrintGCDetails打印GC日誌查看垃圾收集情況。

    示例:
    JVM Args: -Xms20m -Xmx20m -Xmn10m
    堆大小共20m,其中新生代10m,老年代10m,默認-XX:SurvivorRatio=8決定了新生代中Eden區與一個Survivor區的空間比例是8:1

  2. 大對象直接進入老年代,如長字符串及數組,jvm提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配內存,避免在Eden與Survivor之間發生大量內存複製。

  3. 長期存活的對象進入老年代 虛擬機給每個對象定義了一個對象年齡計數器。如果對象在Eden出生並經過一次Minor GC後仍然存活,並且能被Survivor容納,將被移動到Survivor空間,並且對象年齡設爲1,對象在Survivor空間每熬過一次Minor GC,年齡就增加1歲,當年齡到達一定長度(默認15),將會被晉升都老年代。如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到PretenureSizeThreshold中要求的年齡。

  4. 空間分配擔保,在發生Minor GC之前,虛擬機會先檢查老年代最大可用連續空間是否大於新生代所有對象總空間,如果是則Minor GC是安全的。如果不是虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於,將嘗試進行一次Minor GC,儘管這次Minor GC是有風險的;如果小於或者HandlePromotionFailure設置不允許冒險,那這是要進行一次Full GC。因爲在極端的情況下,即新生代所有對象都存活,就需要把Survivor無法容納的對象直接放入老年代。

參考

《深入理解java虛擬機 JVM高級特性與最佳實踐》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章