JVM垃圾回收器
一、基础核心概念
1.1 JVM内存模型
JVM 运行时数据区划分两大板块:线程私有区域、线程共享区域;其中只有堆和方法区会发生GC,其余区域线程消亡自动释放。
线程私有区域
-
程序计数器:当前线程执行的字节码行号,无GC、无OOM,JVM唯一无内存溢出的区域。
-
虚拟机栈:方法执行的数据结构,存储栈帧;栈帧包含局部变量表、操作数栈、动态链接、方法返回地址;线程退出后栈内存自动释放。
-
本地方法栈:结构与虚拟机栈一致,专门服务于Native本地方法。
线程共享区域
-
堆(Heap):所有对象实例、数组分配区域,GC主战场;元空间不属于堆内存。
-
分代模型:新生代(Eden、2块Survivor)、老年代。
-
对象分配规则:优先Eden分配、TLAB线程私有缓冲区分配、逃逸分析栈上分配、大对象直接进入老年代、长期存活对象晋升老年代。
-
-
方法区(Method Area):存储类元信息、运行时常量池、静态变量、即时编译代码。
- 元空间(Metaspace):JDK8 彻底替代永久代,使用操作系统本地内存,默认无内存上限,解决永久代OOM问题;可通过
-XX:MaxMetaspaceSize手动限制大小。
- 元空间(Metaspace):JDK8 彻底替代永久代,使用操作系统本地内存,默认无内存上限,解决永久代OOM问题;可通过
对象生命周期
-
分配:
new对象优先存入Eden区,超大对象直接进入老年代。 -
存活:通过可达性分析判断,GC Roots可达则判定存活。
-
回收:不可达对象,经标记、清理、压缩后释放内存。
1.2 可达性分析与GC Roots
主流JVM采用可达性分析算法判定对象存活,摒弃传统引用计数法(解决循环引用无法回收问题)。
GC Roots存活根对象(面试必背)
-
虚拟机栈:栈帧中局部变量表引用的对象。
-
方法区:静态变量、常量池引用的对象。
-
本地方法栈:JNI本地方法引用的对象。
-
同步锁:Synchronized 持有的锁对象。
-
虚拟机内部:Class对象、异常对象、系统内置引用。
四种引用类型(强度从高到低)
-
强引用:
Object obj = new Object();,只要引用存在,永不回收,默认引用。 -
软引用(SoftReference):内存充足不回收,内存溢出前强制回收,适合做缓存。
-
弱引用(WeakReference):只要发生GC,无论内存是否充足都会回收,典型应用:ThreadLocalMap。
-
虚引用(PhantomReference):无引用能力,仅用于跟踪对象回收状态,配合引用队列使用。
二、垃圾回收算法
2.1 基础算法
标记-清除(Mark-Sweep)
-
流程:遍历标记存活对象,统一清除未标记垃圾对象。
-
优点:算法简单、无需移动对象。
-
缺点:产生大量内存碎片,分配大对象容易OOM;扫描效率低。
标记-整理(Mark-Compact)
-
流程:标记存活对象,将存活对象向内存一端平移压缩,清空边界以外垃圾。
-
优点:无内存碎片,内存空间规整。
-
缺点:移动对象成本高、效率低,适合存活率高的老年代。
复制(Copying)
-
流程:将内存划分为两块同等大小区域,只使用其中一块;GC时将存活对象复制到空闲区域,清空原区域。
-
优点:无碎片、复制效率高,无需复杂标记计算。
-
缺点:内存利用率仅有50%,浪费空间;适合存活率极低的新生代。
2.2 分代收集理论
依据对象存活周期差异化回收,不同年代适配不同算法,是现代GC核心思想。
-
新生代(Young Gen):对象朝生夕死、存活率极低,采用复制算法;代表收集器:Parallel Scavenge、G1。
-
老年代(Old Gen):对象存活时间长、存活率高,采用标记-清除/标记-整理;代表收集器:Parallel Old、CMS、G1。
-
元空间:存储类元数据,回收频率极低,仅卸载无用类、释放无用类加载器。
补充:对象晋升规则
-
年龄晋升:默认存活15次MinorGC晋升老年代(对象头4bit存储分代年龄,最大值15)。
-
动态年龄判断:Survivor区相同年龄对象总和超过50%,大于等于该年龄对象直接晋升。
三、主流垃圾收集器
3.1 G1垃圾收集器(Garbage-First)
JDK9 默认收集器,面向大内存、低延迟设计,兼顾吞吐量与停顿时间。
核心特性
-
分区(Region):将堆划分为固定大小Region(1MB~32MB),逻辑分代、物理不分代;包含Eden、Survivor、Old、Humongous巨型分区。
-
可预测停顿:通过
-XX:MaxGCPauseMillis自定义最大停顿时间,可控STW。 -
Remembered Set(RS):基于卡表(Card Table)实现,记录跨代引用,避免MinorGC全局扫描老年代。
-
并发标记:业务线程与GC线程并发执行,减少STW耗时。
垃圾回收流程
G1 只有两类GC:YoungGC、MixedGC;并发标记为MixedGC前置准备阶段。
-
Young GC(STW)
-
根扫描:快速扫描GC Roots;
-
更新RS:处理脏卡队列,刷新跨代引用;
-
对象复制:存活对象转移至Survivor区,达标对象晋升老年代;
-
清空Eden:回收全部Eden分区内存。
-
-
并发标记周期(MixedGC前置)
-
初始标记(STW):标记GC Roots直接可达对象,耗时极短;
-
根区域扫描(并发):扫描Survivor区引用的老年代对象;
-
并发标记:GC线程遍历对象图,标记全部存活对象;
-
重新标记(STW):修正并发期间引用变更,采用SATB算法;
-
筛选清理(STW):统计Region垃圾占比,筛选高垃圾分区。
-
-
Mixed GC(STW)
-
分区筛选:根据停顿时间目标,选取新生代+垃圾最多的老年代Region;
-
复制回收:迁移存活对象,批量释放选中分区内存。
-
关键机制
-
SATB(快照起始算法):标记开始瞬间保存对象引用快照,解决并发标记过程中对象消失导致的漏标问题。
-
巨型对象(Humongous):占用空间超过1/2 Region的对象,单独分配专属分区,避免复制开销;默认不回收,MixedGC统一清理。
-
卡表(Card Table):堆内存划分512B卡片,记录引用变更,是Remembered Set的底层实现。
3.2 ZGC垃圾收集器(Z Garbage Collector)
JDK11实验特性、JDK15正式发布、JDK21默认开启分代ZGC;面向超大堆、亚毫秒级低延迟设计。
核心特性
-
亚毫秒级停顿:停顿时间不随堆内存增大而变长,标准停顿时间 <10ms。
-
染色指针(Colored Pointers):复用64位指针高位,存储标记、重定位状态,无需额外内存存储元数据。
-
读屏障(Load Barrier):访问对象时触发拦截,处理引用修正、对象迁移,无写屏障开销。
-
逻辑不分代:JDK21前无物理分代,JDK21+分代优化,针对性回收短期存活小对象。
垃圾回收流程
-
标记阶段
-
初始标记(STW):仅标记GC Roots直接引用对象,耗时与堆大小无关;
-
并发标记:遍历对象图,读屏障拦截并发修改,标记存活对象;
-
再标记(STW):短暂修正并发期间引用变化,耗时通常<1ms。
-
-
重定位阶段
-
并发准备重定位:筛选垃圾率高的内存页,组成重分配集;
-
初始重定位(STW):优先迁移GC Roots引用对象;
-
并发重定位(核心):GC线程+业务线程协同迁移对象;业务线程访问旧对象时,读屏障自动迁移对象、修正指针,实现自愈;
-
并发清理:对象迁移完成,原内存页立即释放归还系统。
-
-
延迟重映射:遗留旧指针不立即修正,延迟至下一轮GC并发标记阶段处理,降低单次停顿压力。
关键机制
- 染色指针状态
| 指针高位 | 含义 |
|---|---|
| 00 | Marked0 |
| 01 | Marked1 |
| 10 | Remapped |
- 读屏障伪代码
|
|
四、性能调优与故障排查
4.1 调优目标
-
低延迟:ZGC、Shenandoah、G1(MixedGC),适用于互联网高并发业务。
-
高吞吐量:Parallel GC,适用于离线批处理、大数据计算。
-
低内存占用:合理设置堆区间、规避内存泄漏、优化对象存活率。
4.2 关键参数
G1 核心参数
|
|
ZGC 核心参数
|
|
4.3 故障排查工具
-
jstat -gcutil <pid>:实时查看GC次数、内存占用、GC耗时。 -
jmap -dump:live,format=b,file=heap.bin <pid>:生成堆转储快照,分析内存对象。 -
jstack <pid>:导出线程栈,排查死锁、线程阻塞。 -
-Xlog:gc*:打印详细GC日志,示例:-Xlog:gc*:file=gc.log:utctime,level=debug。 -
可视化工具:JVisualVM、MAT(堆内存分析)、GCEasy(GC日志分析)。
4.4 常见问题与解决方案
Full GC频繁(高频考点)
-
老年代空间不足:调大堆内存、优化大对象生成逻辑。
-
元空间溢出:调整
-XX:MaxMetaspaceSize,排查动态生成类。 -
直接内存泄漏:限制
-XX:MaxDirectMemorySize,排查NIO堆外内存。 -
额外诱因:晋升失败、空间担保失败、手动调用
System.gc()。
CPU使用率高
-
G1混合GC过多:适当调大停顿时间,减少MixedGC触发频率。
-
ZGC重定位压力大:排查高频创建短期大对象,优化业务代码。
内存泄漏
通过MAT分析堆转储文件,定位大对象、静态集合常驻对象、循环引用对象。
五、前沿技术与未来趋势
-
Shenandoah:低延迟收集器,采用Brooks Pointer转发指针实现并发压缩,停顿时间与堆大小无关;劣势:吞吐量偏低。
-
Epsilon GC:无操作GC,不执行垃圾回收;用于性能测试、短生命周期微任务。
-
分代ZGC:JDK21正式默认,区分新生代、老年代,优化小对象回收效率,降低GC开销。
-
Loom项目:虚拟线程,轻量化用户线程,降低线程创建与切换开销,优化GC线程调度模型。
六、最佳实践与总结
收集器选择标准(企业实战选型)
| 业务场景 | 推荐收集器 | 选型理由 |
|---|---|---|
| 堆 < 4GB、离线任务 | Parallel GC | 高吞吐量、实现简单、资源占用低 |
| 堆 4GB~32GB、通用业务 | G1 | 平衡延迟与吞吐量,可控停顿时间 |
| 堆 > 32GB、高并发低延迟 | ZGC / Shenandoah | 亚毫秒级停顿,适配超大堆、实时业务 |
调优原则
-
先监控,后调优:基于GC日志、监控指标定位问题,禁止盲目改参数。
-
避免过早优化:优先优化业务代码、减少无效对象创建,再调整JVM参数。
-
关注核心指标:吞吐量、STW延迟、内存使用率、GC触发频率。
架构师决策点
-
业务评估:区分实时联机业务(低延迟)、离线批处理业务(高吞吐)。
-
容量规划:预估业务峰值,预留堆内存冗余,避免内存溢出。
-
持续监控:接入APM工具(Prometheus+Grafana),实时监控GC指标,动态调优。