目录
[1. 引用计数算法(理论参考)](#1. 引用计数算法(理论参考))
[2. 可达性分析算法(JVM 实际使用)](#2. 可达性分析算法(JVM 实际使用))
[3. 对象的"缓刑"机制](#3. 对象的“缓刑”机制)
[1. 分代回收策略](#1. 分代回收策略)
[2. 手动触发与注意事项](#2. 手动触发与注意事项)
[1. 基础算法对比](#1. 基础算法对比)
[2. 分代收集理论](#2. 分代收集理论)
[3. 新生代回收:Apple式复制算法](#3. 新生代回收:Apple式复制算法)
[1. CMS 收集器(低停顿优先)](#1. CMS 收集器(低停顿优先))
[2. G1 收集器(平衡吞吐与延迟)](#2. G1 收集器(平衡吞吐与延迟))
[3. 收集器对比](#3. 收集器对比)
[1. 参数配置示例](#1. 参数配置示例)
[2. 常见问题排查](#2. 常见问题排查)
[3. 工具推荐](#3. 工具推荐)
一、如何判定对象"生死"?
垃圾收集(GC)的核心是识别无用对象。JVM 通过两种算法判断对象是否存活:
1. 引用计数算法(理论参考)
-
原理 :
每个对象维护一个引用计数器,被引用时计数器 +1,引用失效时 -1。计数器为 0 时判定为可回收。
-
缺点 :
无法解决循环引用问题(如对象 A 引用 B,B 也引用 A)。
-
Java 未采用 :主流 JVM 均使用 可达性分析算法。
2. 可达性分析算法(JVM 实际使用)
-
原理 :
从 GC Roots 出发,遍历对象引用链。若对象无法被 GC Roots 关联,则判定为可回收。
-
GC Roots 对象类型:
-
虚拟机栈中的局部变量(如方法参数、局部变量)。
-
方法区中静态变量引用的对象。
-
方法区中常量引用的对象(如字符串常量池)。
-
本地方法栈中 JNI 引用的对象(Native 方法)。
-
同步锁持有的对象(
synchronized
锁对象)。 -
Java 虚拟机内部对象(如系统类加载器、异常对象)。
-
3. 对象的"缓刑"机制
-
finalize()
方法 :若对象重写
finalize()
且未被调用过,JVM 会将其放入F-Queue
,由 Finalizer 线程触发该方法。 -
逃脱机会 :
在
finalize()
中重新建立与 GC Roots 的引用链,可避免被回收(仅一次)。
java
public class RescueObject {
public static RescueObject hook;
@Override
protected void finalize() throws Throwable {
super.finalize();
hook = this; // 在 finalize 中自我拯救
}
}
二、引用类型与回收策略
Java 提供 四种引用类型,控制对象生命周期与回收优先级:
引用类型 | 特点 | 回收时机 | 典型场景 |
---|---|---|---|
强引用 | Object obj = new Object() ,默认引用类型 |
对象不可达时回收 | 普通对象创建 |
软引用 | SoftReference<Object> ref = new SoftReference<>(obj) |
内存不足时回收(OOM 前触发) | 缓存(如图片缓存) |
弱引用 | WeakReference<Object> ref = new WeakReference<>(obj) |
下一次 GC 时回收 | 临时缓存(如 WeakHashMap) |
虚引用 | PhantomReference<Object> ref = new PhantomReference<>(obj, queue) |
随时可能回收,需配合 ReferenceQueue 使用 | 堆外内存回收监听(如 DirectByteBuffer) |
三、何时触发垃圾回收?
GC 触发时机由 内存区域分配策略 和 JVM 配置参数 共同决定:
1. 分代回收策略
区域 | GC 类型 | 触发条件 |
---|---|---|
新生代 | Minor GC | Eden 区空间不足 |
老年代 | Major GC | 老年代空间不足(通常伴随 Full GC) |
整堆 | Full GC | 方法区不足、老年代空间不足、手动调用 System.gc() |
2. 手动触发与注意事项
-
System.gc()
:建议 JVM 触发 Full GC(不保证立即执行)。 -
风险:频繁 Full GC 会导致应用停顿(Stop-The-World),需谨慎使用。
四、垃圾回收算法与实现
1. 基础算法对比
算法 | 步骤 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
标记-清除 | 标记存活对象 → 清除未标记对象 | 简单 | 内存碎片化 | 老年代(CMS) |
复制算法 | 存活对象复制到新区域 → 清空原区域 | 无碎片,高效 | 内存利用率 50% | 新生代(Survivor) |
标记-整理 | 标记存活对象 → 整理到内存一端 | 无碎片化 | 整理耗时 | 老年代(Serial Old) |
2. 分代收集理论
-
弱分代假说:绝大多数对象朝生夕灭(新生代)。
-
强分代假说:熬过多次 GC 的对象难以消亡(老年代)。
-
分代设计:
-
新生代:使用复制算法(Eden + Survivor)。
-
老年代:使用标记-清除或标记-整理算法。
-
3. 新生代回收:Apple式复制算法
-
内存划分:
- Eden : Survivor1 : Survivor2 = 8:1:1(默认)。
-
回收流程:
-
新对象分配至 Eden 区。
-
Eden 满时触发 Minor GC,存活对象复制到 Survivor1。
-
下次 Minor GC 时,Eden 和 Survivor1 存活对象复制到 Survivor2,并清空原区域。
-
对象年龄达到阈值(默认 15)后晋升老年代。
-
五、主流垃圾收集器详解
1. CMS 收集器(低停顿优先)
-
目标:最小化应用停顿时间。
-
算法:标记-清除。
-
工作流程:
-
初始标记(STW):标记 GC Roots 直接关联对象。
-
并发标记:遍历对象图(与用户线程并发)。
-
重新标记(STW):修正并发标记期间变动的引用。
-
并发清除:清理垃圾(与用户线程并发)。
-
-
缺点:
-
内存碎片化(需定期 Full GC 整理)。
-
并发阶段占用 CPU 资源。
-
2. G1 收集器(平衡吞吐与延迟)
-
目标:可预测的停顿时间(如 200ms 内)。
-
内存布局:将堆划分为多个 Region(默认 2048 个)。
-
工作流程:
-
初始标记(STW):标记 GC Roots 直接关联对象。
-
并发标记:遍历对象图(与用户线程并发)。
-
最终标记(STW):处理剩余引用变更。
-
筛选回收(STW):选择性价比高的 Region 回收。
-
-
优势:
-
支持大内存(TB 级)。
-
通过 Region 划分减少碎片化。
-
3. 收集器对比
收集器 | 算法 | 区域 | 特点 | 适用场景 |
---|---|---|---|---|
CMS | 标记-清除 | 老年代 | 低停顿,但碎片化严重 | 响应敏感型应用 |
G1 | 标记-整理 | 全堆 | 可预测停顿,兼顾吞吐与延迟 | 大内存、低延迟应用 |
六、调优建议与工具推荐
1. 参数配置示例
bash
# 使用 G1 收集器,堆内存 4G,目标停顿 200ms
java -Xmx4G -Xms4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar
# 启用 CMS 收集器
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode
2. 常见问题排查
-
频繁 Full GC:
-
检查内存泄漏(如静态集合未清理)。
-
调整新生代与老年代比例(
-XX:NewRatio
)。
-
-
长时间 STW:
-
切换低延迟收集器(如 G1/ZGC)。
-
减少堆内存大小(权衡吞吐与停顿)。
-
3. 工具推荐
-
监控工具:VisualVM、JConsole、Prometheus + Grafana。
-
日志分析:GCeasy、GCViewer。
-
诊断工具:Arthas、MAT(Memory Analyzer Tool)。
七、总结
-
生死判定 :可达性分析是核心,
finalize()
是最后的逃生机会。 -
引用分级:软、弱引用优化内存敏感场景。
-
算法选择:分代理论平衡效率与资源利用率。
-
收集器选型:CMS 适合低延迟,G1 适合大内存与可预测停顿。
核心原则:结合业务需求与监控数据动态调优,避免盲目配置。