JVM 组成与垃圾回收详解
一、JVM 中有什么?
1. 类加载子系统
负责加载 .class
文件到内存,包含加载、链接(验证、准备、解析)、初始化三个阶段。
2. 运行时数据区(核心)
- 堆:对象实例存储区域(GC 主战场)
- 方法区:JDK8 后改为元空间(Metaspace),使用本地内存存储类元数据
- 虚拟机栈:存储方法调用栈帧(局部变量表、操作数栈等)
- 程序计数器:当前线程执行的字节码行号指示器
- 本地方法栈:Native 方法调用
3. 执行引擎
解释器(逐行解释字节码)、JIT 编译器(热点代码编译为机器码)、GC(垃圾回收器)
4. 本地接口(JNI)
调用操作系统本地库(如 C/C++ 代码)
二、垃圾回收(GC)
1. 是什么?
自动回收堆内存中不再使用的对象,释放资源防止内存泄漏。
2. 解决什么问题?
- 手动内存管理易导致内存泄漏/溢出
- 提升开发效率,避免
delete/free
操作 - 保证程序稳定性(如避免
OutOfMemoryError
)
3. 对象存活判定
- 引用计数法(Java 未采用):循环引用问题
- 可达性分析(Java 采用):从 GC Roots(栈、静态变量等)出发扫描引用链
三、常用 GC 算法
1. 标记-清除(Mark-Sweep)
- 过程:标记存活对象 → 清除未标记对象
- 问题:内存碎片
- 场景:老年代 CMS 回收器
2. 标记-复制(Mark-Copy)
- 过程:内存分为两块,存活对象复制到另一块 → 清空当前块
- 优点:无碎片
- 缺点:空间利用率 50%
- 场景:新生代(Eden + Survivor)
3. 标记-整理(Mark-Compact)
- 过程:标记存活对象 → 向一端移动 → 清理边界外内存
- 优点:无碎片
- 缺点:移动对象成本高
- 场景:老年代 Serial Old、G1
四、JDK8+ 主流 GC 回收器
回收器 | 区域 | 算法 | 特点 | JDK版本 |
---|---|---|---|---|
Parallel | 新生代/老年代 | 复制+标记整理 | 吞吐量优先 | 8默认 |
CMS | 老年代 | 标记清除 | 低延迟,碎片问题 | 8 |
G1 | 全堆 | 分区+标记整理 | 平衡吞吐/延迟,JDK9+默认 | 9+ |
ZGC | 全堆 | 着色指针+读屏障 | <10ms 停顿,TB级堆 | 11+ |
Shenandoah | 全堆 | 转发指针+读屏障 | 低延迟,与ZGC竞争 | 12+ |
五、GC 参数调优
1. 核心参数
bash
# 堆内存设置
-Xms4g -Xmx4g # 初始堆=最大堆(避免动态扩容)
-XX:NewRatio=2 # 老年代:新生代=2:1
-XX:SurvivorRatio=8 # Eden:Survivor=8:1:1
# GC 回收器选择
-XX:+UseG1GC # 启用 G1(JDK9+默认)
-XX:+UseZGC # 启用 ZGC(JDK15+生产可用)
# 目标停顿时间(G1/ZGC)
-XX:MaxGCPauseMillis=200 # 期望最大停顿 200ms
2. 调优步骤
- 监控:使用
jstat -gc <pid>
或 VisualVM 分析 GC 日志 - 定位问题:
- 频繁 Full GC:老年代空间不足 → 增大堆或调整
NewRatio
- Young GC 时间长:Eden 过大 → 减小
-Xmn
- 频繁 Full GC:老年代空间不足 → 增大堆或调整
- 优化策略:
- 高吞吐场景:Parallel GC + 增大堆
- 低延迟场景:G1/ZGC + 限制
MaxGCPauseMillis
3. GC 日志分析
bash
-XX:+PrintGCDetails -Xloggc:gc.log
示例日志:
java
[GC (Allocation Failure) [PSYoungGen: 1024K->512K(1536K)]
2048K->1024K(5632K), 0.002s]
Allocation Failure
:Eden 区空间不足触发 Young GCPSYoungGen
:Parallel Scavenge 回收新生代1024K->512K
:回收前占用 → 回收后占用(含 Survivor)
六、重要注意事项
- 元空间溢出:JDK8 后
Metaspace
使用本地内存,需监控-XX:MaxMetaspaceSize
- G1 调优:
- 避免设置过小
-XX:G1HeapRegionSize
(默认根据堆计算) - 混合回收阈值:
-XX:G1MixedGCLiveThresholdPercent=85
(存活对象低于 85% 才回收)
- 避免设置过小
- ZGC 限制:
- JDK15 前为实验特性
- 需要 Linux x64/macOS 环境
- 生产建议:
- 优先使用 G1(JDK8+)
- 超大规模堆(>32GB)用 ZGC/Shenandoah
总结
组件 | 核心要点 |
---|---|
JVM 结构 | 堆(对象存储)、元空间(类元数据)、栈(方法调用) |
GC 目的 | 自动管理堆内存,防止内存泄漏 |
GC 算法 | 复制(新生代)、标记清除/整理(老年代) |
JDK8+ GC | Parallel(吞吐优先)、G1(平衡)、ZGC(超低延迟) |
调优核心 | 根据场景选回收器,监控 GC 日志,调整堆/代大小和停顿目标 |
最佳实践:
- 生产环境 JDK11+ 默认用 G1
- 先用
-Xms=-Xmx
固定堆大小- 通过
MaxGCPauseMillis
控制延迟- 定期分析 GC 日志(工具:GCeasy、Grafana)