目錄
一、如何判斷對象可以回收
(一)引用計數法
(二)可達性分析算法
二、垃圾回收算法
(一)標記清除
(二)標記整理
(三)復制
(四)分代垃圾回收
(五)相關 VM 參數
三、垃圾回收器
(一)串行(Serial)
(二)吞吐量優先
(三)響應時間優先
(四)G1
核心特點
(一)G1 垃圾回收階段
(二)核心階段詳解
(1) 年輕代GC(Young GC)
(2) 并發標記周期
(3) 混合回收(Mixed GC)
(三)G1關鍵調優參數
基礎配置
區域配置
并發周期控制
四、垃圾回收調優
(一)調優領域
(二)確定目標
(三)最快的 GC
(四)新生代調優
(五)老年代調優
一、如何判斷對象可以回收
(一)引用計數法
給對象中添加一個引用計數器:
- 每當有一個地方引用它,計數器就加 1;
- 當引用失效,計數器就減 1;
- 任何時候計數器為 0 的對象就是不可能再被使用的。
這個方法實現簡單,效率高,但是目前主流的虛擬機中并沒有選擇這個算法來管理內存,其最主要的原因是它很難解決對象之間循環引用的問題。
所謂對象之間的相互引用問題,如下面代碼所示:除了對象 objA
和 objB
相互引用著對方之外,這兩個對象之間再無任何引用。但是他們因為互相引用對方,導致它們的引用計數器都不為 0,于是引用計數算法無法通知 GC 回收器回收他們。
public class ReferenceCountingGc {Object instance = null;public static void main(String[] args) {ReferenceCountingGc objA = new ReferenceCountingGc();ReferenceCountingGc objB = new ReferenceCountingGc();objA.instance = objB;objB.instance = objA;objA = null;objB = null;}
}
(二)可達性分析算法
Java通過GC Roots
對象作為起點,向下搜索引用鏈。若對象與GC Roots
無引用鏈相連,則判定為可回收。
GC Roots包括:
-
虛擬機棧中引用的對象(如局部變量)
-
方法區中類靜態屬性引用的對象
-
方法區中常量引用的對象
-
本地方法棧中JNI引用的對象
-
同步鎖持有的對象
-
活躍線程對象
-
引用類型與回收策略
引用類型 回收條件 示例 強引用 永不回收(除非顯式置為 null
)Object obj = new Object()
軟引用(Soft) 內存不足時回收 SoftReference<Object> ref
弱引用(Weak) 下次GC必然回收 WeakReference<Object> ref
虛引用(Phantom) 僅用于回收跟蹤 PhantomReference<Object> ref
-
對象死亡過程
1. 強引用
- 只有所有 GC Roots 對象都不通過【強引用】引用該對象,該對象才能被垃圾回收
2. 軟引用(SoftReference)
- 僅有軟引用引用該對象時,在垃圾回收后,內存仍不足時會再次出發垃圾回收,回收軟引用對象
- 可以配合引用隊列來釋放軟引用自身
3. 弱引用(WeakReference)
- 僅有弱引用引用該對象時,在垃圾回收時,無論內存是否充足,都會回收弱引用對象
- 可以配合引用隊列來釋放弱引用自身
4. 虛引用(PhantomReference)
- 必須配合引用隊列使用,主要配合 ByteBuffer 使用,被引用對象回收時,會將虛引用入隊, 由 Reference Handler 線程調用虛引用相關方法釋放直接內存
5. 終結器引用(FinalReference)
- 無需手動編碼,但其內部配合引用隊列使用,在垃圾回收時,終結器引用入隊(被引用對象 暫時沒有被回收),再由 Finalizer 線程通過終結器引用找到被引用對象并調用它的 finalize 方法,第二次 GC 時才能回收被引用對象
二、垃圾回收算法
(一)標記清除
定義: Mark Sweep
- 速度較快
- 會造成內存碎片
(二)標記整理
定義:Mark Compact
- 速度慢
- 沒有內存碎片
(三)復制
定義:Copy
- 不會有內存碎片
- 需要占用雙倍內存空間
(四)分代垃圾回收
-
年輕代(Young Generation)
-
對象分配:新對象在Eden區創建
-
回收算法:復制算法(Minor GC)
-
過程:
-
Eden(伊甸園) + S0(From)存活對象復制到S1(To)
-
清空Eden(伊甸園)和S0(From)
-
S0(From)與S1(To)角色交換
-
-
晉升條件:對象年齡(經歷GC次數)達到閾值(默認15),或Survivor區空間不足
-
-
老年代(Old Generation)
-
存放長期存活對象
-
回收算法:標記-清除或標記-整理(Major GC/Full GC)
-
觸發條件:年輕代晉升失敗或空間不足
-
- 對象首先分配在伊甸園區域
- 新生代空間不足時,觸發 Minor GC,伊甸園和 From 存活的對象使用copy復制到 To 中,存活的對象年齡加1并且交換 From 和 To
- Minor GC 會引發 Stop The World,暫停其它用戶的線程,等垃圾回收結束,用戶線程才恢復運行
- 當對象壽命超過閾值時,會晉升至老年代,最大壽命是15(4bit)
- 當老年代空間不足,會先嘗試觸發 Minor GC,如果之后空間仍不足,那么觸發 Full GC,STW的時間更長
算法 | 原理 | 優點 | 缺點 |
---|---|---|---|
標記-清除 | 1. 標記所有活動對象 2. 清除未標記對象 | 實現簡單 | 內存碎片化,分配效率低 |
復制 | 內存分為兩塊,每次使用一塊。GC時將存活對象復制到另一塊,清空當前塊。 | 無碎片,高效 | 內存利用率僅50% |
標記-整理 | 1. 標記活動對象 2. 將存活對象向內存一端移動 3. 清理邊界外內存 | 無碎片,內存利用率高 | 對象移動開銷大 |
分代收集 | 結合多種算法,按對象生命周期分區管理 | 綜合性能最優(主流方案) | 實現復雜 |
(五)相關 VM 參數
含義 | 參數 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存區比例(動態) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存區比例 | -XX:SurvivorRatio=ratio |
晉升閾值 | -XX:MaxTenuringThreshold=threshold |
晉升詳情 | -XX:+PrintTenuringDistribution |
GC詳情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
三、垃圾回收器
1. 串行(Serial)
- 單線程
- 堆內存較小,適合個人電腦
2. 吞吐量優先(Parallel Scavenge)
- 多線程
- 堆內存較大,多核 cpu
- 讓單位時間內,STW 的時間最短 0.2 0.2 = 0.4,垃圾回收時間占比最低,這樣就稱吞吐量高
3. 響應時間優先(CMS)
- 多線程
- 堆內存較大,多核 cpu
- 盡可能讓單次 STW 的時間最短 0.1 0.1 0.1 0.1 0.1 = 0.5
(一)串行(Serial)
-XX:+UseSerialGC = Serial + SerialOld
(二)吞吐量優先(Parallel Scavenge)
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
(三)響應時間優先(CMS)
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
(四)G1
G1 (Garbage-First)?是Java 7引入、Java 9成為默認的垃圾回收器,專為大內存、低延遲場景設計,替代了傳統的CMS回收器。
核心特點
-
分區模型:將堆劃分為多個等大小的Region(默認約2048個)
-
分代收集:保留新生代/老年代概念,但物理不連續
-
可預測停頓:通過
-XX:MaxGCPauseMillis
設置目標停頓時間 -
并發標記:與應用程序線程并行工作
-
混合回收:同時清理新生代和老年代區域
(一)G1 垃圾回收階段
Young Collection
- 會 STW
Young Collection + CM
- 在 Young GC 時會進行 GC Root 的初始標記
- 老年代占用堆空間比例達到閾值時,進行并發標記(不會 STW),由下面的 JVM 參數決定
-XX:InitiatingHeapOccupancyPercent=percent (默認45%)
Mixed Collection
會對 E、S、O 進行全面垃圾回收
- 最終標記(Remark)會 STW
- 拷貝存活(Evacuation)會 STW
-XX:MaxGCPauseMillis=ms
(二)核心階段詳解
(1) 年輕代GC(Young GC)
-
觸發條件:Eden區滿時
-
過程:
-
STW(Stop-The-World)開始
-
構造回收集(Collection Set = Eden + Survivor)
-
復制存活對象到新Survivor區
-
年齡達到閾值(默認15)的對象晉升老年代
-
(2) 并發標記周期
-
觸發條件:老年代占用超過IHOP閾值(默認45%)
-
階段組成:
-
初始標記(STW):伴隨Young GC進行
-
根區域掃描:掃描Survivor區引用
-
并發標記:標記存活對象(與應用并發)
-
最終標記(STW):處理SATB緩沖區
-
清理(STW):統計完全空閑Region
-
(3) 混合回收(Mixed GC)
-
回收包含年輕代Region +?高收益的老年代Region
-
根據垃圾比例排序Region(Garbage-First原則)
-
多次執行直到滿足回收目標
(三)G1關鍵調優參數
基礎配置
參數 | 默認值 | 說明 |
---|---|---|
-XX:+UseG1GC | - | 啟用G1回收器 |
-Xms ?/?-Xmx | - | 堆初始/最大大小(建議設相同值) |
-XX:MaxGCPauseMillis | 200ms | 目標最大停頓時間 |
區域配置
參數 | 默認值 | 說明 |
---|---|---|
-XX:G1HeapRegionSize | 1-32MB | Region大小(2的冪次) |
-XX:G1NewSizePercent | 5% | 新生代最小占比 |
-XX:G1MaxNewSizePercent | 60% | 新生代最大占比 |
并發周期控制
參數 | 默認值 | 說明 |
---|---|---|
-XX:InitiatingHeapOccupancyPercent | 45% | IHOP閾值(觸發并發標記) |
-XX:G1MixedGCLiveThresholdPercent | 85% | Region存活對象低于此值才回收 |
-XX:G1HeapWastePercent | 5% | 可回收垃圾占比閾值(停止Mixed GC) |
四、垃圾回收調優
(一)調優領域
- 內存
- 鎖競爭
- cpu
- 占用 io
(二)確定目標
- 【低延遲】還是【高吞吐量】,選擇合適的回收器
- CMS,G1,ZGC
- ParallelGC
- Zing
(三)最快的 GC
答案是不發生 GC
查看 FullGC 前后的內存占用,考慮下面幾個問題
數據是不是太多?
- resultSet = statement.executeQuery("select * from 大表 limit n")
數據表示是否太臃腫?
- 對象圖
- 對象大小 16 Integer 24 int 4
是否存在內存泄漏?
- static Map map =
- 軟
- 弱
- 第三方緩存實現
(四)新生代調優
-
新生代的特點
- 所有的 new 操作的內存分配非常廉價
- TLAB thread - local allocation buffer
- 死亡對象的回收代價是零
- 大部分對象用過即死
- Minor GC 的時間遠遠低于 Full GC
- 所有的 new 操作的內存分配非常廉價
-
越大越好嗎?
-
新生代能容納所有【并發量*(請求 - 響應)】的數據
-
幸存區大到能保留【當前活躍對象 + 需要晉升對象】
-
晉升閾值配置得當,讓長時間存活對象盡快晉升
- -XX:MaxTenuringThreshold = threshold
- -XX:+PrintTenuringDistribution
Desired survivor size 48286924 bytes, new threshold 10 (max 10)age 1: 28992024 bytes, 28992024 total age 2: 1366864 bytes, 30358888 total age 3: 1425912 bytes, 31784800 total?…
(五)老年代調優
以 CMS 為例
- CMS 的老年代內存越大越好
- 先嘗試不做調優,如果沒有 Full GC 那么已經…,否則先嘗試調優新生代
- 觀察發生 Full GC 時老年代內存占用,將老年代內存預設調大 1/4 ~ 1/3
- -XX:CMSInitiatingOccupancyFraction=percent