Java常見GC算法_垃圾收集器及內存分配_G1垃圾收集器

常見GC算法

  • 引用計數法: 每個對象都有一個計數器, 對象被引用一次, 計數器+1, 當對象引用失敗一次. 計數器-1, 當對象計數器等於0, 說明對象沒有被應用, 就可GC
優:  運行過程中, 可隨時檢查對象計數器, 進行GC, 且GC過程, 應用無需暫停, 執行速度快(單個對象GC不會影響其他對象), 內存不足, OOM
缺: 存在循環引用問題(A引用B, B引用A, A=null, B=null. A,B永遠不會GC), 隨時都在GC, 佔用CPU
  • 標記清除: 先標記( 從root進行可達性分析, 標記被引用的對象), 再清除(清除那些沒被引用的對象)
    在這裏插入圖片描述
優; 解決循環引用問題
缺: 效率低下, 需要遍歷所有對象, 碎片化嚴重, 清理出來的內存不連續, 當new大對象時, 容易爆OOM 
  • 標記壓縮: 標記需要回收的區域, 進行回收, 將碎片化的空間進行壓縮
    在這裏插入圖片描述
優: 解決標記清除的碎片化問題
缺: 需要移動內存位置, 效率降低
  • 複製算法: 將空間一分爲二(只使用其中一塊空間進行存儲), 進行清除的時候, 將存活對象複製到另外一塊空間, 原本的空間全部清除, 解決移動內存問題
    在這裏插入圖片描述
優: 解決標記清除存在的內存移動問題
缺: 對空間的浪費較爲嚴重, 不適用內存空間垃圾較少的情況

複製算法在JVM年輕代的應用

在這裏插入圖片描述

1. GC開始前, 對象分佈於Eden, s0區, s1區爲空
2. GC開始, Eden中的存活對象全部複製到s1中, s0區中存活對象根據他們的年齡值決定去向
	使用-XX:MaxTenuringThreshold設置年齡閾值, 超過該閾值, s0中對象移到老年代, 未達到, 對象則移動到s1中
3. GC完成, 清空Eden, s0區域, s0與s1交換角色, 重複步驟1, 直到"s1"被填滿, 然後將"s1"中對象全部移到老年代中
優: 垃圾對象較多時, 效率高, 無碎片化
缺: 垃圾較少, 不適用, 如:老年代, s0/s1一個時刻只能使用其中一塊, 內存使用率低

分代算法: 年輕代採用複製清除, 老年代使用標記清除/壓縮

垃圾收集器及內存分配

  • 串行垃圾收集器: GC過程, 只有一個線程工作, 且應用要停止運行(Stop-the-world), 等待GC完成.
/**
 * 測試GC收集器
 * @author regotto
 */
public class GcTest {

    public static void main(String[] args) {
        ArrayList<Object> objects = new ArrayList<>();
        while (true) {
            if (System.currentTimeMillis() % 2 == 0) {
            //產生大量廢棄對象
                objects.clear();
            } else {
                for (int i = 0; i < 10000; i++) {
                    Properties properties = new Properties();
                    properties.put("key:" + i, "value:" + System.currentTimeMillis());
                    objects.add(properties);
                }
            }
            try {
                Thread.sleep(new Random().nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

設置VM Optional: 使用串行GC器, 打印GC細節
在這裏插入圖片描述
運行結果如下:

在這裏插入圖片描述
日誌解讀(使用第一行數據):

GC: 年輕代GC;	Full GC: 所有空間全部GC
DefNew: 使用串行GC器
4416k -> 512k(4928k): GC前年輕代對象佔4416k空間, GC年輕代對象佔512k空間, 總共4928k空間
0.0019950secs: GC花費時間
7318k -> 3975k(15872k): 堆空間GC情況
  • 並行垃圾回收器: 在串行GC的基礎上, 變爲多線程進行GC操作(存在Stop-the-world), 其餘與串行GC一樣
    • ParNew垃圾收集器
      只能在年輕代工作(只是將串行GC變爲並行GC), 使用-XX:+UseParNewGC設置, 老年代依舊採用串行GC
      在這裏插入圖片描述
      測試代碼在上一個代碼的基礎上修改VM options
      運行過程中, 發現相比於SerialGC, ParNew在GC上存在一定的提升
    • ParallelGC垃圾收集器
      與ParNew一樣, 新增多個與吞吐量相關的參數, 操作更加靈活
-XX:+UseParallelGC 年輕代使用ParallelGC垃圾回收器,老年代使用串行回收器。
-XX:+UseParallelOldGC 年輕代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。 
-XX:MaxGCPauseMillis 設置最大的垃圾收集時的停頓時間,單位爲毫秒 
	需要注意的是,ParallelGC爲了達到設置的停頓時間,可能會調整堆大小或其他 的參數,
	如果堆的大小設置的較小,就會導致GC工作變得很頻繁,反而可能會 影響到性能。 該參數使用需謹慎。 
-XX:GCTimeRatio 設置垃圾回收時間佔程序運行時間的百分比,公式爲1/(1+n)。 它的值爲0~100之間的數字,默認值爲99,也就是垃圾回收時間不能超過1% 
-XX:UseAdaptiveSizePolicy 自適應GC模式,垃圾回收器將自動調整年輕代、老年代等參數,達到吞吐量、 堆大小、停頓時間之間的平衡。 一般用於,手動調整參數比較困難的場景,讓收集器自動進行調整。

在這裏插入圖片描述
測試代碼與前一個一樣, 只修改VM options, 運行結果如下:
在這裏插入圖片描述

  • CMS垃圾處理器
    CMS(Concurrent Mark Sweep): 針對老年代(對老年代GC進行改進), 使用標記清除算法, -XX:UseConcurrentMarkSweepGC設置.
    執行過程如下:
    在這裏插入圖片描述
InitialMarking: 標記root, 出現Stop-the-world
Marking: 標記對象, 與應用線程同時運行
preclean: 預清理, 與應用線程同時運行
finalMarking: 再次標記, 由於與應用線程同時運行, 前期的標記並不能解決問題, 此過程出現Stop-the-world
sweeping: 併發清除, 與應用線程同時運行
resizing: 調整堆大小, 清理碎片, 壓縮空間
resetting: 重置, 等待觸發下一次CMS, 與用戶線程同時運行

在這裏插入圖片描述
程序打印的日誌也是按照上面的流程, 程序運行結果如下:
在這裏插入圖片描述

G1垃圾收集器(jdk1.7使用)

G1取消傳統的新生代, 老年代物理劃分, 將內存空間變爲若干個區域, 每個區域包含邏輯上的新生代, 老年代. G1存在YoungGC, MixedGC, FullGC, 在不同的條件下觸發

優: 每一塊區域存在多種狀態(Old, Eden, Humongous, Survivor)解決碎片化問題, 即使在正常處理過程中, 都能解決內存壓縮問題

在這裏插入圖片描述

G1的YoungGC

Eden區空間耗盡觸發, EdenGC, Eden中數據移動到Survivor區(Survivor滿了, 數據移動到新的Survivor區, 部分數據移動到Old區), 部分數據移動到Old區, 當前Eden清空, 變爲未使用區
在這裏插入圖片描述

RememberSet(記憶集合)

RememberSet解決新生代尋找根對象的問題, 每一個區域初始化都生成一個RememberSet, 該集合保存其他對象引用"我"的記錄, 掃描RememberSet就能得出對象之間的引用關係, 而不再需要對新生代, 老年代中所有對象進行掃描.
在這裏插入圖片描述

G1的MixedGC

爲避免堆內存被耗盡, JVM啓動MixedGC, 回收所有的Young區, 回收部分Old區(MixedGC不是FullGC).
使用-XX:InitiatingHeapOccupancyPercent=n(老年代佔整個堆大小百分比閾值) 決定MixedGC什麼時候觸發
MixedGC分爲2個步驟: 全局併發標記(前5個步驟), 拷貝存活對象(第6個步驟)
在這裏插入圖片描述

G1相關參數

-XX:+UseG1GC 使用 G1 垃圾收集器 
-XX:MaxGCPauseMillis 設置期望達到的最大GC停頓時間指標(JVM會盡力實現,但不保證達到),默認 值是 200 毫秒。
-XX:G1HeapRegionSize=n 設置的 G1 區域的大小。值是 2 的冪,範圍是 1 MB 到 32 MB 之間。目標是根 據最小的 Java 堆大小劃分出約 2048 個區域。 默認是堆內存的1/2000。
-XX:ParallelGCThreads=n 設置 STW 工作線程數的值。將 n 的值設置爲邏輯處理器的數量。n 的值與邏輯 處理器的數量相同,最多爲 8。
-XX:ConcGCThreads=n 設置並行標記的線程數。將 n 設置爲並行垃圾回收線程數 (ParallelGCThreads) 的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=n 設置觸發標記週期的 Java 堆佔用率閾值。默認佔用率是整個 Java 堆的 45%。

G1日誌輸出參數

‐XX:+PrintGC 輸出GC日誌
‐XX:+PrintGCDetails 輸出GC的詳細日誌 
‐XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式) 
‐XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013‐05‐ 04T21:53:59.234+0800) 
‐XX:+PrintHeapAtGC 在進行GC的前後打印出堆的信息 
‐Xloggc:../logs/gc.log 日誌文件的輸出路徑

測試

在這裏插入圖片描述
代碼運行完畢自動將GC日誌輸入到項目下的gc.log中
使用GC Easy進行分析(http://gceasy.io)
將gc.log上傳該網站, 就能進行GC分析, 獲取分析報告

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