一、垃圾收集算法
1. 分代收集理论
- 核心思想:根据对象存活周期不同,将 Java 堆分为新生代和老年代,适配不同垃圾收集算法
- 分代依据 :
- 新生代:对象存活时间短,每次 GC 大量对象(近 99%)回收,适合复制算法
- 老年代:对象存活概率高,无额外空间担保,适合标记 - 清除 / 标记 - 整理算法
- 性能差异:标记 - 清除 / 标记 - 整理算法比复制算法慢 10 倍以上
2. 三大基础垃圾收集算法
(1)标记 - 复制算法
- 原理:将内存分为大小相等的两块,每次使用一块;内存耗尽时,复制存活对象到另一块,清空原块
- 适用场景:新生代(Serial、ParNew、Parallel Scavenge 收集器)
- 优点:效率高,无内存碎片
- 缺点:内存利用率低(仅 50%)
(2)标记 - 清除算法
- 原理 :
- 标记阶段:标记存活对象(或需回收对象)
- 清除阶段:统一回收未标记(或已标记)对象
- 适用场景:老年代(CMS 收集器)
- 优点:实现简单
- 缺点 :
- 效率低(标记对象过多时)
- 产生大量不连续内存碎片
(3)标记 - 整理算法
- 原理:标记阶段与标记 - 清除一致,后续将所有存活对象向一端移动,清理边界外内存
- 适用场景:老年代(Serial Old、Parallel Old 收集器)
- 优点:无内存碎片,内存利用率高
- 缺点:增加对象移动开销,效率低于复制算法
3. 算法对比总结
| 算法 | 优点 | 缺点 | 适用代际 |
|---|---|---|---|
| 标记 - 复制 | 效率高、无碎片 | 内存利用率低 | 新生代 |
| 标记 - 清除 | 实现简单、无需移动 | 效率低、产生碎片 | 老年代 |
| 标记 - 整理 | 无碎片、利用率高 | 效率低、有移动开销 | 老年代 |
二、垃圾收集器
1. 收集器分类与核心特性
(1)新生代收集器
| 收集器 | 线程模型 | 算法 | 核心特点 | 参数配置 |
|---|---|---|---|---|
| Serial | 单线程 | 标记 - 复制 | 简单高效、STW 时间长 | -XX:+UseSerialGC |
| ParNew | 多线程 | 标记 - 复制 | 可与 CMS 配合、多线程并发 | -XX:+UseParNewGC |
| Parallel Scavenge | 多线程 | 标记 - 复制 | 注重吞吐量、自适应调节 | -XX:+UseParallelGC |
(2)老年代收集器
| 收集器 | 线程模型 | 算法 | 核心特点 | 参数配置 |
|---|---|---|---|---|
| Serial Old | 单线程 | 标记 - 整理 | 单线程、作为 CMS 后备 | -XX:+UseSerialOldGC |
| Parallel Old | 多线程 | 标记 - 整理 | 注重吞吐量、Parallel 配套 | -XX:+UseParallelOldGC |
| CMS | 并发 | 标记 - 清除 | 低停顿、并发收集 | -XX:+UseConcMarkSweepGC |
2. 重点收集器详解
(1)Serial 收集器(串行收集器)
- 线程模型:单线程(GC 线程 + STW 所有用户线程)
- 算法实现:新生代复制算法,老年代标记 - 整理算法
- 优点:无线程交互开销,单线程效率高
- 缺点:STW 时间长,不适用于多核服务器
- 适用场景:客户端应用、小型程序
(2)Parallel 系列收集器(吞吐量优先)
- Parallel Scavenge(新生代)
- 多线程收集,默认线程数 = CPU 核数(可通过 - XX:ParallelGCThreads 调整)
- 核心目标:吞吐量(用户代码运行时间 / CPU 总时间)
- 支持自适应调节(自动优化停顿时间 / 吞吐量)
- Parallel Old(老年代)
- 多线程 + 标记 - 整理算法
- JDK8 默认老年代收集器
- 适用场景:注重吞吐量、CPU 资源敏感的服务
(3)ParNew 收集器(CMS 配套)
- 本质是 Parallel Scavenge 的多线程版本,唯一能与 CMS 配合的新生代收集器
- 支持多线程并发收集,STW 时间比 Serial 短
- 适用场景:Server 模式、需与 CMS 配合的服务
(4)CMS 收集器(低停顿优先)
- 核心目标:最短回收停顿时间(用户体验优先)
- 线程模型:并发收集(垃圾收集线程与用户线程同时运行)
- 算法实现:标记 - 清除算法

- 初始标记:STW,记录 GC Roots 直接引用对象(速度快)
- 并发标记:无 STW,遍历对象图(耗时久,与用户线程并发)
- 重新标记:STW,修正并发标记的漏标(比初始标记久,远短于并发标记)
- 并发清理:无 STW,清理未标记对象(新对象标记为黑色)
- 并发重置:重置标记数据
- 优点:并发收集、低停顿
- 缺点 :
- CPU 资源敏感(与服务抢资源)
- 产生浮动垃圾(需下次 GC 清理)
- 内存碎片(可通过参数优化)
- 可能出现 Concurrent Mode Failure(触发 Serial Old 回收)
- 核心参数 :
-XX:ConcGCThreads:并发 GC 线程数-XX:+UseCMSCompactAtFullCollection:FullGC 后整理碎片-XX:CMSInitiatingOccupancyFraction:老年代使用率阈值(默认 92%)-XX:+CMSScavengeBeforeRemark:Remark 前触发 Minor GC
3. 收集器选择原则
- 无万能收集器,需根据场景选择:
- 客户端 / 小型应用:Serial + Serial Old
- 吞吐量优先:Parallel Scavenge + Parallel Old
- 低停顿 / 用户体验优先:ParNew + CMS
- 大内存(>4G)+ 低停顿:G1/ZGC/Shenandoah(扩展)
三、底层核心机制
1. 三色标记算法(并发标记核心)
(1)核心概念
- 标记规则 :按对象访问状态分为三种颜色:
- 黑色:已访问,且所有引用已扫描(安全存活,不指向白色对象)
- 灰色:已访问,但存在未扫描引用
- 白色:未访问(分析结束后仍为白色则需回收)
- 核心问题 :并发标记时,用户线程修改对象引用导致 "多标" 或 "漏标"

(2)问题解决方案
- 多标(浮动垃圾) :
- 原因:并发标记中,GC Roots 销毁导致部分对象失去引用但已标记
- 影响:不影响正确性,需下次 GC 清理
- 处理:新对象直接标记为黑色(本轮不清理)
- 漏标(严重 bug) :
- 原因:并发标记中,对象引用关系变化导致部分可达对象未标记
- 解决方案:
- 增量更新(CMS 使用):黑色对象新增指向白色对象的引用时,记录引用并重新扫描
- 原始快照(SATB,G1/Shenandoah 使用):灰色对象删除指向白色对象的引用时,记录引用并重新扫描
(3)实现机制:读写屏障
-
写屏障 :赋值操作前后插入处理逻辑(AOP 思想)
- 增量更新:
post_write_barrier记录新引用对象 - SATB:
pre_write_barrier记录旧引用对象
- 增量更新:
-
读屏障:读取成员变量前插入处理逻辑(ZGC 使用),记录读取的对象
-
代码模型:
// 写屏障示例
void oop_field_store(oop* field, oop new_value) {
pre_write_barrier(field); // SATB记录旧值
*field = new_value;
post_write_barrier(field, new_value); // 增量更新记录新值
}// 读屏障示例
oop oop_field_load(oop* field) {
pre_load_barrier(field); // 记录读取对象
return *field;
}
2. 记忆集与卡表(跨代引用优化)
(1)核心问题
- 新生代 GC 时,需扫描老年代中指向新生代的跨代引用(直接扫描老年代效率低)
(2)记忆集(Remember Set)
- 数据结构:记录从非收集区到收集区的指针集合
- 作用:避免扫描整个非收集区,仅需处理记忆集中的跨代引用
(3)卡表(Card Table)
- 实现方式:字节数组
CARD_TABLE[],每个元素对应 512 字节(2^9)的 "卡页" - 标记规则:卡页中存在跨代引用时,对应元素设为 1(变脏),否则为 0
- 维护机制:通过写屏障触发卡表更新

四、JVM 参数优化实战(ParNew+CMS)
1. 亿级流量订单系统场景
- 硬件配置:4 核 8G 服务器,JVM 分配 4G 内存
- 业务特征:每秒产生 60MB 对象,大促峰值 300 单 / 秒
- 核心目标:减少 Full GC 频率,控制 STW 时间
2. 优化后参数配置
-Xms3072M -Xmx3072M # 堆内存固定3G(避免动态扩容)
-Xmn2048M # 新生代2G(占堆66.7%,减少Minor GC频率)
-Xss1M # 线程栈大小
-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M # 元空间固定
-XX:SurvivorRatio=8 # Eden:S0:S1=8:1:1
-XX:MaxTenuringThreshold=5 # 对象5次Minor GC后进入老年代
-XX:PretenureSizeThreshold=1M # 1M以上大对象直接进入老年代
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC # 启用ParNew+CMS
-XX:CMSInitiatingOccupancyFraction=92 # 老年代使用率92%触发Full GC
-XX:+UseCMSCompactAtFullCollection # Full GC后整理碎片
-XX:CMSFullGCsBeforeCompaction=3 # 3次Full GC后整理一次
3. 优化思路解析
- 新生代扩容:减少 Minor GC 次数,避免对象频繁进入老年代
- 调整对象晋升年龄:短期存活对象(几秒内)在 Minor GC 中回收
- 大对象直接进入老年代:避免新生代复制开销
- CMS 参数优化:平衡 Full GC 频率与碎片整理开销
