
垃圾收集(Garbage Collection,GC)是 Java 虚拟机(JVM)内存管理的核心机制,主要针对 Java 堆 和 方法区 进行回收。线程私有的程序计数器、虚拟机栈和本地方法栈随线程生命周期结束而自然消失,无需 GC。本文将详细探讨 GC 的判定依据、算法、收集器及内存分配策略。
对象是否需要回收
1. 引用计数算法
- 原理:对象维护一个引用计数器,被引用时加 1,引用失效时减 1,计数为 0 时可回收。
- 缺陷:无法解决循环引用问题,例如两个对象互相引用,计数永不为 0。
- 结论:JVM 不采用此算法。
2. 可达性分析算法
- 原理 :从 GC Roots 开始搜索,可达对象存活,不可达对象死亡。
- GC Roots :
- 虚拟机栈中的引用对象。
- 本地方法栈中的引用对象。
- 方法区中的静态属性和常量引用对象。
- 应用:JVM 主流采用此算法。
3. 引用类型
Java 提供四种引用类型,影响对象回收:
- 强引用 :
new
创建的对象,不会回收。 - 软引用 :
SoftReference
,内存不足时回收。 - 弱引用 :
WeakReference
,下次 GC 时回收,常用于缓存(如WeakHashMap
)。 - 虚引用 :
PhantomReference
,仅用于接收回收通知。
4. 方法区回收
- 目标:废弃常量和无用类。
- 类卸载条件 :
- 所有实例已回收。
- 类加载器已回收。
Class
对象未被引用。
- 控制 :通过
-Xnoclassgc
参数决定是否卸载。
5. finalize() 方法
- 作用:对象回收前可执行的自救方法。
- 问题:运行代价高、不确定性大,建议避免使用。
垃圾收集算法
1. 性能指标
- 停顿时间:GC 导致的程序暂停时长。
- 吞吐量:用户代码运行时间占比。
2. 标记-清除(Mark-Sweep)
- 过程:标记需回收对象,然后清除。
- 缺点:效率低,产生内存碎片。
3. 标记-整理(Mark-Compact)
- 过程:标记后将存活对象移至一端,清理边界外内存。
- 优点:解决碎片问题。
- 缺点:整理开销大。
4. 标记-复制(Copying)
- 过程:内存分两块,存活对象复制到空闲块,清理已用块。
- 优点:无碎片。
- 缺点:内存利用率低(改进为 Eden + 2 Survivor)。
5. 分代收集
- 原理:根据对象生命周期分代,年轻代用复制算法,老年代用标记-清除或标记-整理。
- 堆结构 :
- 新生代:Eden + 2 Survivor,默认比例 8:1:1。
- 老年代:存放长生命周期对象。
- 永久代:JDK 8 前的方法区实现,现为元空间。
垃圾收集器
HotSpot 提供多种垃圾收集器,各有适用场景:
1. 串行收集器(Serial)
- 特点:单线程,Stop-The-World,适用于 Client 模式。
- 组合 :
- Serial :年轻代,复制算法,
-XX:+UseSerialGC
。 - Serial Old:老年代,标记-整理。
- Serial :年轻代,复制算法,
2. 并行收集器
- 目标:高吞吐量,Server 模式默认。
- 组合 :
- Parallel Scavenge :年轻代,复制算法,
-XX:+UseParallelGC
。 - Parallel Old :老年代,标记-整理,
-XX:+UseParallelOldGC
。
- Parallel Scavenge :年轻代,复制算法,
- 参数 :
-XX:MaxGCPauseMillis
:控制停顿时间。-XX:GCTimeRatio
:设置吞吐量。-XX:+UseAdaptiveSizePolicy
:自适应调整。
3. 并发标记清除(CMS)
- 目标:最短停顿时间。
- 组合 :
-XX:+UseConcMarkSweepGC
,ParNew(年轻代)+ CMS(老年代)+ Serial Old(备用)。 - 步骤 :
- 初始标记(停顿)。
- 并发标记。
- 重新标记(停顿)。
- 并发清除。
- 缺点:内存碎片、浮动垃圾,需预留空间。
4. G1 收集器
- 特点 :兼顾吞吐量和停顿时间,JDK 9+ 默认,
-XX:+UseG1GC
。 - 分区:堆划分为多个 Region,动态分配角色。
- 步骤 :
- 初始标记。
- 并发标记。
- 最终标记。
- 筛选回收(优先高价值 Region)。
- 优点:无碎片,可预测停顿。
收集器对比
收集器 | 类型 | 代 | 算法 | 目标 | 场景 |
---|---|---|---|---|---|
Serial | 串行 | 年轻代 | 复制 | 低停顿 | 单核 Client 模式 |
Serial Old | 串行 | 老年代 | 标记-整理 | 低停顿 | CMS 备用 |
ParNew | 并行 | 年轻代 | 复制 | 低停顿 | 与 CMS 配合 |
Parallel Scavenge | 并行 | 年轻代 | 复制 | 高吞吐量 | 后台运算 |
Parallel Old | 并行 | 老年代 | 标记-整理 | 高吞吐量 | 后台运算 |
CMS | 并发 | 老年代 | 标记-清除 | 低停顿 | Web 服务端 |
G1 | 并发 | 全部 | 标记-整理+复制 | 低停顿+吞吐量 | 服务端应用 |
内存分配与回收策略
1. Minor GC
- 触发:Eden 区满。
- 过程 :存活对象复制到 Survivor,年龄达阈值(默认 15,
-XX:MaxTenuringThreshold
)晋升老年代。
2. Full GC
- 触发条件 :
System.gc()
(建议性,可禁用)。- 老年代空间不足。
- 方法区(元空间)不足。
- Minor GC 晋升平均大小超老年代剩余空间。
- 对象大于 Survivor 和老年代可用空间。
3. 分配策略
- Eden 优先:新对象分配在 Eden。
- 大对象直入老年代 :
-XX:PretenureSizeThreshold
。 - 长期存活晋升:年龄超阈值。
- 动态年龄判定:Survivor 半满时提前晋升。
- 空间担保:老年代为 Minor GC 提供担保。
结语
JVM 的垃圾收集机制通过可达性分析、分代收集和多样化的收集器,高效管理内存。串行适合小型应用,并行追求吞吐量,CMS 和 G1 优化停顿时间。理解 GC 原理和策略,有助于调优程序性能,避免内存溢出等问题。