【JVM】之 垃圾回收


內存佈局圖:
在這裏插入圖片描述


一、垃圾回收算法



(1) 標記-清除法

該算法會從每個 GC Roots出發, 依次標記引用關係的對象, 最後將沒有被標記的對象清除

此算法帶來的後果:

  1. 帶來大量的空間碎片

若需要分配一個較大連續空間時容易觸發 FGC

就好比在操作系統中對碎片空間的整理


(2) 標記-整理法(Mark-Copy)

此類算法類似計算機的磁盤整理

步驟如下: (不考慮存活對象超過S0S1)

  1. 第一次 Minior GC, Eden區被清空, Eden存活對象移至S0
  2. 第二次 Minior GC, Eden區被清空, Eden存活對象和S0存活對象移至S1, S0區清空
  3. 之後, 存活對象在S0S1中交換

EdenS0S1內存容量分配
Eden 佔 80%, S0 佔 10%, S1 佔 10%

爲什麼這樣區分?
爲了更大利用內存容量.



二、垃圾回收器(Garbage Collector)


垃圾回收器(Garbage Collector)是實現垃圾回收算法並應用在JVM環境中的內存管理模塊


(1) Serial回收器

Serial回收器是一個主要應用於 YGC 的垃圾回收器, 採用串行單線程的方式完成 GC 任務

“Stop The World” 簡稱 STW, 即垃圾回收的某個階段會暫停整個應用程序的執行

現在寫後臺 Java 系統幾乎不用

主要流程如圖:
在這裏插入圖片描述


(2) ParNew回收器

ParNew回收器主要用於新生代, 採用多線程機制

理論上4核CPU, 支持4個垃圾回收線程並行執行

執行Minor GC, 也採用 STW, 會把系統程序的工作線程全部停掉, 禁止程序繼續運行創建新的對象, 採用多個垃圾回收線程去進行垃圾回收

-XX:+UseParNewGC: 表示JVM啓動之後對新生代進行垃圾回收

-XX:ParallelGCThreads: 調節ParNew的垃圾回收線程數量

一般採用默認, 例如: 4核CPU 8核CPU 16核CPU, 對應ParNew線程數爲 4、8、16


(3) CMS回收器(Concurrent Mark Sweep Collector)

CMS回收器 是回收停頓時間比較短、用於老年代的垃圾回收器, 採用多線程機制, 性能較好

四個步驟完成垃圾回收:

  1. 初始化標記(Initial Mark)
  2. 併發標記(Concurrent Mark)
  3. 重新標記(Remark)
  4. 併發清除(Concurrent Sweep)

1、3 步的初始化標記和重新標記階段依然會引發 STW,
而 2、4步的併發標記和併發清除兩個階段可以和應用程序併發執行, 也是比較耗時的操作, 但並不影響應用程序的正常執行

CMS 採用 “標記 - 清除算法”, 因此產生大量的空間碎片

-XX:+UseCMSCompactAtFullCollection 強制 JVMFGC完成後對老年代進行壓縮, 執行一次空間碎片整理, 但是空間碎片整理階段也會引發 STW
-XX:+CMSFullGCsBeforeCompaction=n: 在執行了 n 次 FGC後, JVM再在老年代執行空間碎片整理, 減少 STW 次數.


(4) G1回收器(Garbage-First Garbage Collector)

目的:爲了減少Stop the World

Hotspot 在 JDK7 中推出了新一代 G1, 通過-XX:+UseG1GC 開啓

優點:

  1. 和 CMS 相比, G1 具備壓縮功能, 能避免碎片問題
  2. G1 的暫停時間(STW)更加可控
  3. 統一收集新生代和老年代, 採用了更加優秀的算法和設計機制
  4. 可預測的停頓時間,能夠儘可能地在指定時間內完成空間碎片,-XX:MaxGCPauseMills參數來設定,默認值爲 200ms

如圖:G1回收模型內存佈局

G1Java堆空間分割成了若干相同大小的區域,即 region

  1. region包括EdenSurvivorOldHumongous四種類型
  2. Humongous 是特殊的Old類型,專門放置大型對象。
  3. 這樣劃分意味着不需要一個連續的內存空間管理對象

G1採用 Mark-Copy

G1執行時使用 4個 worker併發執行,在初始標記時,還是會觸發STW


1. G1 是如何工作的?

G1 需要知道每個Region有多少垃圾,處理這些垃圾需要多長時間?

根據需要回收對象的大小和回收預估時間,進行選擇回收Region

如圖:
在這裏插入圖片描述


2. 對象什麼時候進入新生代的Region?什麼時候對象進入老年代 Region

剛開始,Region可能誰都不屬於。

  1. 當有對象產生,則分配給了新生代
  2. 觸發 GC
  3. 下一次,同一個Region可能又被分配給了老年代,用來存放老年代的對象

如圖:
在這裏插入圖片描述

新生代進入老年代的條件:

  1. 對象在新生代躲過了很多此的垃圾回收,達到了一定的年齡,可通過-XX:MaxTenuringThreshold設置年齡
  2. 動態年齡判定規則,如果一旦發現某次新生代 GC 過後,存貨對象超過了 Survivor的50%

3. 什麼時候觸發Region GC?什麼時候觸發老年代的Region GC

G1將內存劃分爲多個Region,但還是有 新生代、老年代的區分
新生代裏還是有 EdenSurvivor劃分
觸發垃圾回收的機制也相類似


4. Humongous 大對象Region

G1提供了專門的Region來存放大對象,而不是讓大對象進入老年代OldRegion

大對象的判定規則:

一個大對象超過一個Region大小的 50%,就會被放入大對象專門的Region
比如:
每個Region 2MB,只要一個大對象超過 1MB,就會被放入大對象專門的Region

如圖:
在這裏插入圖片描述



三、問題



(1) 單線程與多線程進行垃圾回收比較

單CPU運行多線程會導致頻繁的線上切換上下文, 有效率開銷

即:

  1. 若在客戶端(客戶端程序), 採用單線程垃圾回收器
  2. 若在服務端(有多核CPU資源), 採用多線程垃圾回收器

(2) ParNew + CMSGC, 如何保證只做 YGC, JVM參數如何配置?

  1. 加大分代年齡, 比如默認 15 加到 30
  2. 修改新生代和老年代的比例, 比如新生代:老年代 = 2:1
  3. 修改 Eden區 和 S0 S1區比例, 例如 6: 2: 2

(3) 如何做到FullGC次數爲0, 只做 YGC?

關鍵點: 讓Survivor區能放下, 不能因爲動態年齡判斷規則直接升入老年代

觀察上線系統, 每秒會新增多少對象在新生代裏, 多長時間觸發一次 Minior GC, 平均每次 Minor GC 之後會有多少對象存活, Survivor區是否可以放下


(4) 有哪些參數需要了解?

2核4G機器, 可提供 JVM 最大內存 2G

  1. 方法區(元空間)
  2. 老年代
  3. 新生代

(5) 爲什麼老年代的 FGC 要比 新生代Minior GC 慢很多, 一般10倍以上?

  1. 併發標記階段, 老年代存活對象多, 追蹤GC Roots 花費要久
  2. 併發清理階段, 老年代不是一次性回收一大片內存, 而是零散
  3. 內存碎片整理, 把大量的存活對象給挪在一起, 空出來連續內存空間, 這個過程需要 STW

(6) 幾個觸發老年代 GC 的時機?

  1. 老年代可用內存小於新生代全部對象的大小, 如果沒開啓空間擔保參數, 會直接觸發FGC, 所有一般空間擔保參數都會打開

  2. 老年代可用內存小於歷次新生代GC後進入老年代的平均對象大小, 此時會提前FGC

  3. 新生代Minior GC後的存活對象大於Survivor, 那麼就會進入老年代, 此時老年內存若不足

  4. 如果老年代可用內存大於歷次新生代GC後進入老年代的對象平均大小, 但是老年代已經使用的內存空間超過了這個參數指定的比例(-XX:CMSInitiatingOccupancyFaction), 也會自動觸發FGC


(7) 多大的對象直接進入老年代?

大對象可以直接進入老年代, 因爲大對象很可能是要長期存活和使用的

一般設置超過 1MB 的對象爲大對象


(8) JVM 參數標準格式?

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGc -XX:+UseConcSweepGC

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