Java垃圾回收机制与算法
垃圾回收(GC)自动回收不再使用的对象,释放内存。
垃圾判断方法
1. 引用计数法
Java
// 对象被引用时计数+1,引用失效时-1
// 计数为0时可回收
// 问题:循环引用无法回收
2. 可达性分析(JVM使用)
从GC Roots开始搜索,不可达的对象可回收。
GC Roots包括:
- 栈中引用的对象
- 方法区静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈JNI引用的对象
Java
GC Roots
↓
┌─────┴─────┐
│ │
对象A 对象B
↓ ↓
对象C 对象D
↓
对象E ← 不可达,可回收
Java引用类型
| 类型 | 描述 | GC行为 |
|---|---|---|
| 强引用 | new创建 | 不会被回收 |
| 软引用 | SoftReference | 内存不足时回收 |
| 弱引用 | WeakReference | GC时回收 |
| 虚引用 | PhantomReference | 无法获取对象,用于跟踪回收 |
Bash
// 强引用
Object obj = new Object(); // 不会被回收
// 软引用
SoftReference<Object> soft = new SoftReference<>(new Object());
// 弱引用
WeakReference<Object> weak = new WeakReference<>(new Object());
// 虚引用
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);
GC算法
1. 标记-清除
Bash
标记阶段:标记需要回收的对象
清除阶段:回收标记的对象
缺点:
- 效率不高
- 内存碎片
text
标记前:│A│B│C│D│E│F│
标记后:│A│ │C│ │E│F│ (B、D标记为垃圾)
清除后:│A│空│C│空│E│F│ (产生碎片)
2. 标记-复制
text
年轻代使用:
- Eden + Survivor0 + Survivor1
- Eden满时,存活对象复制到Survivor
- Survivor满时,复制到另一个Survivor或老年代
优点:无碎片
缺点:空间利用率低
text
Eden区:│A│B│C│垃圾│垃圾│
↓ 复制
Survivor:│A│B│C│空│空│
3. 标记-整理
text
标记阶段:标记存活对象
整理阶段:存活对象向一端移动,清理边界内存
优点:无碎片
缺点:移动成本高
text
标记前:│A│空│B│空│C│空│
整理后:│A│B│C│空│空│空│
分代收集
text
┌───────────────────────────────────────┐
│ 堆 │
├───────────────────────────────────────┤
│ 年轻代(Young Gen) │
│ ┌───────────────────────────────┐ │
│ │ Eden │ Survivor0 │ Survivor1 │ │
│ │ 80% │ 10% │ 10% │ │
│ └───────────────────────────────┘ │
│ → 新对象,频繁GC,复制算法 │
├───────────────────────────────────────┤
│ 老年代(Old Gen) │
│ → 长期存活对象,较少GC,标记-整理 │
└───────────────────────────────────────┘
GC类型
| GC类型 | 触发条件 | 区域 |
|---|---|---|
| Minor GC | Eden满 | 年轻代 |
| Major GC | 老年代满 | 老年代 |
| Full GC | 整个堆满 | 整个堆+方法区 |
常见垃圾收集器
年轻代收集器
| 收集器 | 算法 | 特点 |
|---|---|---|
| Serial | 复制 | 单线程,简单高效 |
| ParNew | 复制 | Serial多线程版 |
| Parallel Scavenge | 复制 | 关注吞吐量 |
老年代收集器
| 收集器 | 算法 | 特点 |
|---|---|---|
| Serial Old | 标记-整理 | 单线程 |
| Parallel Old | 标记-整理 | 关注吞吐量 |
| CMS | 标记-清除 | 低延迟,有碎片 |
新一代收集器
| 收集器 | 区域 | 特点 |
|---|---|---|
| G1 | 整堆 | 分区收集,可预测停顿 |
| ZGC(JDK 11+) | 整堆 | 延迟<10ms |
| Shenandoah(JDK 12+) | 整堆 | 延断停顿 |
CMS收集器流程
text
1. 初始标记(STW):标记GC Roots直接关联的对象
2. 并发标记:并发标记所有可达对象
3. 重新标记(STW):修正并发标记期间的变动
4. 并发清除:并发清除标记对象
G1收集器特点
text
┌─────────────────────────────────────┐
│ G1堆分区 │
├─────────────────────────────────────┤
│ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐│
│ │E│E│S│S│O│O│O│O│H│H│H│H│ │ │ │ ││
│ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘│
│ E=Eden, S=Survivor, O=Old, H=Humongous│
└─────────────────────────────────────┘
特点:
- 分区收集,不连续年轻代/老年代
- 可预测停顿时间
- 无碎片(复制算法)
GC参数配置
text
# 选择收集器
-XX:+UseSerialGC # Serial + Serial Old
-XX:+UseParNewGC # ParNew + Serial Old
-XX:+UseParallelGC # Parallel Scavenge + Parallel Old
-XX:+UseConcMarkSweepGC # ParNew + CMS
-XX:+UseG1GC # G1
# 吞吐量优先
-XX:MaxGCPauseMillis=200 # 最大停顿时间
-XX:GCTimeRatio=99 # GC时间占比
# CMS参数
-XX:CMSInitiatingOccupancyFraction=70 # 老年代70%触发CMS
# G1参数
-XX:MaxGCPauseMillis=200 # 目标停顿时间
-XX:G1HeapRegionSize=4m # Region大小
GC日志分析
text
# 开启GC日志
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
# 示例日志
[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)]
2048K->1024K(10240K), 0.0051234 secs]
注意事项
频繁Full GC是性能问题信号
CMS有内存碎片问题,必要时触发Full GC整理
G1适合大堆(>4GB),可预测停顿
合理设置堆大小减少GC频率
要点总结
- 垃圾判断:可达性分析,从GC Roots搜索不可达对象
- GC算法:标记-清除(碎片)、标记-复制(年轻代)、标记-整理(老年代)
- 分代收集:年轻代频繁Minor GC,老年代Major/Full GC
- 收集器选择:吞吐量用Parallel,延迟用CMS/G1
- 监控GC日志,调优内存参数
📝 发现内容有误?点击此处直接编辑