JVM垃圾回收机制详解
1. 引言
1.1 什么是垃圾回收机制?
垃圾回收(Garbage Collection,GC)是JVM自动管理内存的一种机制。它负责回收不再使用的对象所占用的内存空间,避免内存泄漏,确保程序能够高效运行。
1.2 为什么需要垃圾回收?
在C/C++等语言中,开发者需要手动管理内存,这容易导致:
- 内存泄漏:忘记释放已分配的内存
- 悬空指针:访问已释放的内存
- 双重释放:重复释放同一块内存
JVM通过垃圾回收机制自动管理内存,大大降低了内存管理的复杂性。
2. 垃圾回收的基本概念
2.1 对象存活判定
2.1.1 引用计数法
- 原理:为每个对象维护一个引用计数器
- 优点:实现简单,判断效率高
- 缺点:无法解决循环引用问题
// 循环引用示例classA{Bb;}classB{Aa;}2.1.2 可达性分析算法
- 原理:从GC Roots出发,通过引用链遍历所有可达对象
- GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
2.2 对象的引用类型
| 引用类型 | 回收条件 | 使用场景 |
|---|---|---|
| 强引用 | 永远不会被回收 | 普通对象引用 |
| 软引用 | 内存不足时回收 | 缓存 |
| 弱引用 | 下次GC时回收 | 缓存、监听器 |
| 虚引用 | 随时可能被回收 | 跟踪对象回收事件 |
2.3 对象的生命周期
创建 → 可用 → 不可见 → 不可达 → 回收- 创建阶段:对象被创建并初始化
- 可用阶段:对象被引用,可以被程序访问
- 不可见阶段:对象的引用超出作用域
- 不可达阶段:对象不再被任何GC Root引用
- 回收阶段:垃圾回收器回收对象占用的内存
3. 垃圾回收算法
3.1 标记-清除算法(Mark-Sweep)
3.1.1 算法原理
- 标记阶段:遍历所有对象,标记存活的对象
- 清除阶段:遍历所有对象,回收未标记的对象
3.1.2 优缺点
- 优点:实现简单
- 缺点:
- 效率问题:标记和清除效率都不高
- 空间问题:产生大量内存碎片
3.2 复制算法(Copying)
3.2.1 算法原理
- 将内存分为两块,每次只使用其中一块
- GC时将存活对象复制到另一块内存
- 清除当前使用的内存块
3.2.2 优缺点
- 优点:
- 没有内存碎片
- 分配内存时只需移动堆顶指针,效率高
- 缺点:内存利用率只有50%
3.3 标记-整理算法(Mark-Compact)
3.3.1 算法原理
- 标记阶段:标记所有存活的对象
- 整理阶段:将存活对象向内存一端移动,然后清理边界外的内存
3.3.2 优缺点
- 优点:没有内存碎片,内存利用率高
- 缺点:移动对象需要更新引用,效率较低
3.4 分代收集算法(Generational Collection)
3.4.1 堆内存分代
JVM将堆内存分为三个区域:
- 新生代(Young Generation):新创建的对象存放在这里
- Eden区:对象首先在这里创建
- Survivor区:存放经过GC后存活的对象
- From Space
- To Space
- 老年代(Old Generation):经过多次GC后仍然存活的对象
- 永久代(Permanent Generation):存放类信息、常量、静态变量(JDK 8后被元空间取代)
3.4.2 各代GC策略
- 新生代:使用复制算法
- 当Eden区满时,触发Minor GC
- 存活对象被复制到Survivor区
- 对象每经过一次GC,年龄加1
- 年龄达到阈值(默认15)后,晋升到老年代
- 老年代:使用标记-清除或标记-整理算法
- 当老年代空间不足时,触发Major GC
- Major GC通常伴随Minor GC(Full GC)
4. 垃圾收集器
4.1 Serial收集器
4.1.1 特点
- 单线程收集
- 收集时暂停所有用户线程(Stop The World)
- 简单高效,适合客户端模式
4.1.2 工作流程
用户线程 → GC(暂停用户线程) → 用户线程 → GC(暂停用户线程)...4.2 ParNew收集器
4.2.1 特点
- Serial收集器的多线程版本
- 多个线程并行进行垃圾回收
- 仍然需要暂停用户线程
4.2.2 工作流程
用户线程 → GC(暂停用户线程,多线程并行回收) → 用户线程...4.3 Parallel Scavenge收集器
4.3.1 特点
- 关注吞吐量(用户代码运行时间 / 总时间)
- 自适应调节策略
- 适合后台任务
4.3.2 关键参数
-XX:MaxGCPauseMillis:最大GC停顿时间-XX:GCTimeRatio:GC时间占比-XX:+UseAdaptiveSizePolicy:自适应调节策略
4.4 Serial Old收集器
4.4.1 特点
- Serial收集器的老年代版本
- 单线程,使用标记-整理算法
4.5 Parallel Old收集器
4.5.1 特点
- Parallel Scavenge的老年代版本
- 多线程,使用标记-整理算法
4.6 CMS收集器(Concurrent Mark Sweep)
4.6.1 特点
- 以最短停顿时间为目标
- 并发收集,低停顿
- 使用标记-清除算法
4.6.2 工作流程
初始标记(STW) → 并发标记 → 重新标记(STW) → 并发清除4.6.3 优缺点
- 优点:并发收集,低停顿
- 缺点:
- 对CPU资源敏感
- 无法处理浮动垃圾
- 产生内存碎片
4.7 G1收集器(Garbage-First)
4.7.1 特点
- 面向服务端应用
- 将堆划分为多个Region
- 可预测的停顿时间
- 整体使用标记-整理算法,局部使用复制算法
4.7.2 工作流程
初始标记 → 并发标记 → 最终标记 → 筛选回收4.7.3 关键参数
-XX:+UseG1GC:启用G1收集器-XX:MaxGCPauseMillis:目标停顿时间-XX:G1HeapRegionSize:Region大小
4.8 ZGC收集器
4.8.1 特点
- 超低延迟(停顿时间不超过10ms)
- 支持TB级堆内存
- 使用着色指针和读屏障技术
4.8.2 工作流程
初始标记 → 并发标记 → 并发预备重分配 → 并发重分配 → 并发重映射5. 垃圾回收调优
5.1 关键参数
5.1.1 内存相关
-Xms:初始堆大小-Xmx:最大堆大小-Xmn:新生代大小-XX:SurvivorRatio:Eden与Survivor区比例-XX:NewRatio:老年代与新生代比例
5.1.2 收集器相关
-XX:+UseSerialGC:使用Serial收集器-XX:+UseParNewGC:使用ParNew收集器-XX:+UseParallelGC:使用Parallel Scavenge收集器-XX:+UseConcMarkSweepGC:使用CMS收集器-XX:+UseG1GC:使用G1收集器-XX:+UseZGC:使用ZGC收集器
5.1.3 GC日志
-verbose:gc:输出GC日志-XX:+PrintGCDetails:打印GC详细信息-XX:+PrintGCDateStamps:打印GC时间戳-Xloggc:gc.log:GC日志输出文件
5.2 调优原则
5.2.1 选择合适的收集器
- 客户端应用:Serial收集器
- 服务端应用:G1收集器
- 对延迟敏感:ZGC收集器
5.2.2 合理设置堆大小
- 新生代大小:堆的1/3到1/2
- 老年代大小:堆的1/2到2/3
- 避免频繁Full GC
5.2.3 监控GC活动
- 使用JVisualVM、JConsole等工具
- 分析GC日志
- 监控堆内存使用情况
5.3 常见问题
5.3.1 频繁Full GC
可能原因:
- 老年代空间不足
- 大对象直接进入老年代
- 内存泄漏
解决方案:
- 增加老年代空间
- 优化代码,减少大对象创建
- 检查内存泄漏
5.3.2 GC停顿时间过长
可能原因:
- 堆内存过大
- 对象图结构复杂
- 收集器选择不当
解决方案:
- 选择合适的收集器(如G1、ZGC)
- 调整GC停顿时间目标
- 优化对象结构
6. 总结
6.1 垃圾回收机制的重要性
- 自动管理内存,降低开发复杂度
- 避免内存泄漏和悬空指针
- 提高程序稳定性和可靠性
6.2 选择垃圾收集器的考虑因素
- 延迟要求:低延迟选G1或ZGC
- 吞吐量要求:高吞吐量选Parallel
- 内存大小:大内存选G1或ZGC
- 应用类型:客户端选Serial,服务端选G1
6.3 未来发展趋势
- 更智能的垃圾回收算法
- 更低的停顿时间
- 更好的内存利用效率
- 与容器化技术的深度集成