本文将穿透式解析JVM垃圾回收核心算法,涵盖7大基础算法+4大现代GC实现+3种内存分配策略,通过15张动态示意图+GC日志实战分析,带您彻底掌握JVM内存自动管理机制。
一、GC核心概念体系
1.1 对象存亡判定法则
引用计数法致命缺陷:
// 循环引用导致内存泄漏
class Node {Node next;public static void main(String[] args) {Node a = new Node();Node b = new Node();a.next = b; // a引用计数=1b.next = a; // b引用计数=1a = null; // a引用计数=1(b.next仍引用)b = null; // b引用计数=1(a.next仍引用)// 两个对象永远无法回收!}
}
1.2 对象引用强度分级
引用类型 | 特点 | 回收条件 | 典型应用 |
---|---|---|---|
强引用 | Object obj = new Object() | 永不回收 | 普通对象 |
软引用 | SoftReference<T> | 内存不足时回收 | 缓存系统 |
弱引用 | WeakReference<T> | 下次GC必回收 | WeakHashMap |
虚引用 | PhantomReference<T> | 对象回收跟踪 | DirectByteBuffer清理 |
二、基础垃圾回收算法详解
2.1 标记-清除(Mark-Sweep)
执行流程:
-
标记阶段:遍历GC Roots标记存活对象
-
清除阶段:回收未标记对象内存
内存布局变化:
初始状态: [A][B][C][D] # 4个对象
标记后: [A*][B][C*][D] # *表示存活
清除后: [A][ ][C][ ] # 产生内存碎片
致命缺陷:
-
内存碎片化:导致大对象分配失败
-
执行效率低:两次堆遍历(O(n)复杂度)
2.2 复制算法(Copying)
内存划分:
Eden: [ 新生对象分配区 ]
Survivor0: [ 存活区 ]
Survivor1: [ 存活区 ]
执行过程:
HotSpot实现细节:
-
默认比例:Eden:Survivor = 8:1:1
-
对象年龄计数器:-XX:MaxTenuringThreshold=15
-
分配担保机制:-XX:HandlePromotionFailure
2.3 标记-整理(Mark-Compact)
执行步骤:
-
标记存活对象(同标记-清除)
-
所有存活对象向内存一端移动
-
清理边界外内存
内存布局变化:
标记前: [A][ ][B][C][ ][D]
标记后: [A*][ ][B*][C*][ ][D*]
整理后: [A][B][C][D][ ][ ] # 消除碎片
优势:避免内存碎片
代价:移动对象需更新引用(STW时间更长)
2.4 分代收集理论
核心假设:
-
弱分代假说:绝大多数对象朝生夕死
-
强分代假说:熬过多次GC的对象更难消亡
-
跨代引用假说:跨代引用是极少数
分代布局:
三、现代GC算法实现
3.1 CMS(Concurrent Mark Sweep)
四阶段执行流程:
致命缺陷:
-
内存碎片:使用标记-清除算法
# 解决方案:开启内存整理 -XX:+UseCMSCompactAtFullCollection
-
并发模式失败:老年代空间不足
# 调优参数 -XX:CMSInitiatingOccupancyFraction=70 # 老年代70%时触发 -XX:+UseCMSInitiatingOccupancyOnly
-
浮动垃圾:并发清理阶段新产生的垃圾
3.2 G1(Garbage-First)
革命性设计:
核心流程:
调优关键参数:
-XX:G1HeapRegionSize=4m # Region大小
-XX:MaxGCPauseMillis=200 # 目标暂停时间
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发标记的堆使用率
3.3 ZGC(Z Garbage Collector)
三大黑科技:
-
染色指针(Colored Pointers)
// 64位指针结构 | 18位保留 | 1位Finalizable | 1位Remap | 1位Marked1 | 1位Marked0 | 42位地址 |
-
内存多重映射
# 不同视图映射同一物理内存 $ cat /proc/<pid>/maps | grep heap
-
并发对象转移
// 对象移动时通过指针自愈机制更新引用
执行阶段:
3.4 Shenandoah GC
创新点:
-
Brooks指针:每个对象头含转发指针
struct Object {void* forward_ptr; // 指向新位置// ...其他字段 }
-
并发压缩算法:无需STW完成堆压缩
性能对比:
GC名称 | 最大暂停时间 | 吞吐量损失 | JDK支持 |
---|---|---|---|
CMS | 100-500ms | 10-20% | ≤JDK14 |
G1 | 50-200ms | <15% | ≥JDK9 |
ZGC | <1ms | <15% | ≥JDK15 |
Shenandoah | <1ms | <10% | OpenJDK |
四、GC算法实战分析
4.1 内存分配策略
对象优先Eden分配:
// -XX:+PrintGCDetails 日志
public class Allocation {private static final int _1MB = 1024 * 1024;public static void main(String[] args) {byte[] a1 = new byte[2 * _1MB]; // 分配在Edenbyte[] a2 = new byte[2 * _1MB]; // 分配在Edenbyte[] a3 = new byte[2 * _1MB]; // 触发Minor GC}
}
大对象直接进老年代:
-XX:PretenureSizeThreshold=3145728 # 3MB以上对象直接进老年代
长期存活对象晋升:
// -XX:MaxTenuringThreshold=1
public class TenuringThreshold {public static void main(String[] args) {byte[] a1 = new byte[_1MB / 4];byte[] a2 = new byte[4 * _1MB];byte[] a3 = new byte[4 * _1MB]; // 触发Minor GCa3 = null;a3 = new byte[4 * _1MB]; // 再次触发Minor GC}
}
4.2 GC日志深度解读
CMS日志分析:
[GC (Allocation Failure) [ParNew: 367616K->40960K(367616K), 0.0468480 secs]-> 新生代回收,暂停46ms
[GC (CMS Initial Mark) [1 CMS-initial-mark: 524289K(786432K)] -> 初始标记阶段
[CMS-concurrent-mark: 1.234/1.543 secs] -> 并发标记耗时1.543秒
[GC (CMS Final Remark) [YG occupancy: 275342 K (367616 K)]-> 重新标记阶段
[CMS-concurrent-sweep: 0.876/0.987 secs]-> 并发清除耗时0.987秒
G1日志关键指标:
[GC pause (G1 Evacuation Pause) (young), 0.0234159 secs][Parallel Time: 22.3 ms][Ext Root Scanning: 3.5 ms][Update RS: 0.2 ms][Processed Buffers: 43][Scan RS: 1.8 ms][Code Root Scanning: 0.7 ms][Object Copy: 15.1 ms] # 对象复制耗时[Eden: 2048.0M(2048.0M)->0.0B(2048.0M) Survivors: 0.0B->102.0M Heap: 4096.0M(8192.0M)->2054.0M(8192.0M)]
五、高级调优技巧
5.1 避免Full GC的黄金法则
-
老年代空间担保:
-XX:-HandlePromotionFailure # JDK6后已失效
-
元空间监控:
jstat -gcmetacapacity <pid> # 查看元空间使用
-
堆外内存控制:
-XX:MaxDirectMemorySize=128m
5.2 GC选择策略矩阵
应用场景 | 推荐GC | 参数配置 |
---|---|---|
小型应用 | Serial | -XX:+UseSerialGC |
响应优先Web服务 | G1 | -XX:+UseG1GC -MaxGCPauseMillis=100 |
大内存服务 | ZGC/Shenandoah | -XX:+UseZGC -Xmx32g |
低延迟交易系统 | ZGC | -XX:+UseZGC -XX:SoftMaxHeapSize=4g |
六、常见生产问题解决方案
6.1 频繁Full GC排查
诊断步骤:
-
jstat -gcutil <pid> 1000
观察内存趋势 -
jmap -histo:live <pid>
查看对象直方图 -
jmap -dump:format=b,file=heap.bin <pid>
导出堆转储 -
MAT分析支配树查找泄漏点
6.2 GC调优实战案例
场景:电商大促期间每2小时Full GC
分析:监控显示老年代每次GC后剩余空间不足10%
解决方案:
# 原配置
-Xms8g -Xmx8g -XX:NewRatio=2# 优化后
-Xms12g -Xmx12g
-XX:NewRatio=1 # 增大新生代
-XX:SurvivorRatio=6 # 增大Eden
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150