结论
- 算法选择:根据应用场景选择回收器——高吞吐量选 Parallel,低延迟选 CMS/G1,大内存选 ZGC/Shenandoah。
- 版本适配:JDK8 后优先使用 G1,避免 CMS 的内存碎片问题。
- 参数调优:结合监控工具(如 VisualVM)分析 GC 日志,逐步调整参数,避免盲目设置。
何为STW
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)
通过合理配置 GC 算法与参数,可显著提升应用性能与稳定性,减少因内存管理不当导致的系统卡顿或崩溃风险。
一、GC 算法概述
垃圾回收(Garbage Collection, GC)是自动管理内存的核心机制,其核心目标是识别并释放不再使用的对象内存。常见的 GC 算法可分为以下几类:
1. 引用计数法
graph TD
A[对象A被创建] -->|初始引用计数 = 1| B[引用计数: 1]
C[对象B引用对象A] -->|引用计数 +1| B
D[对象C引用对象A] -->|引用计数 +1| B
E[对象B释放对A的引用] -->|引用计数 -1| B
F[对象C释放对A的引用] -->|引用计数 -1| B
G[引用计数 = 0] -->|触发回收| H[对象A被GC回收]
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style H fill:#fbb,stroke:#333
原理:通过记录每个对象被引用的次数来判断其是否为垃圾。当引用计数降为零时,对象即可回收。
优点:实现简单,实时性较高。
缺点:
- 循环引用问题(如对象 A 引用 B,B 引用 A,导致计数无法归零);最终会导致内存泄漏
- 频繁的计数增减操作影响性能。
应用场景:Python、ActionScript3 等语言采用,但未被 Java 采用。
2. 标记-清除算法
graph TD
subgraph 标记阶段
A[根节点 Roots] --> B[对象A]
A --> C[对象B]
B --> D[对象C]
C --> E[对象D]
B --> F[对象E]
G[对象F] %% 不可达对象
H[对象G] %% 不可达对象
%% 标记可达对象
style B fill:#9f9,stroke:#333 %% 绿色表示存活
style C fill:#9f9,stroke:#333
style D fill:#9f9,stroke:#333
style E fill:#9f9,stroke:#333
style F fill:#9f9,stroke:#333
style G fill:#f99,stroke:#333 %% 红色表示垃圾
style H fill:#f99,stroke:#333
end
subgraph 清除阶段
I[清除未标记对象] --> J[释放对象F]
I --> K[释放对象G]
L[内存整理后状态] --> M[对象A/B/C/D/E保留]
end
%% 流程连接
标记阶段 --> 清除阶段
classDef marked fill:#9f9,stroke:#333;
classDef unmarked fill:#f99,stroke:#333;
原理:分为两个阶段:
- 标记阶段:从根节点(如栈、静态变量等)出发,遍历所有可达对象并标记;
- 清除阶段:回收未被标记的对象内存。
缺点:内存碎片化严重,可能影响大对象分配效率。
适用场景:适用于存活对象较多的老年代。
3. 标记-压缩算法
graph TD
subgraph 标记-压缩算法
A[标记阶段] -->|遍历根节点标记存活对象| B[内存碎片状态]
B --> C[压缩阶段]
C -->|移动存活对象至内存一端| D[整理后连续内存]
D --> E[清除边界外碎片]
style A fill:#cff,stroke:#333
style B fill:#fcc,stroke:#333
style C fill:#ffc,stroke:#333
style D fill:#cfc,stroke:#333
end
优化点:在标记-清除的基础上增加内存整理步骤,将存活对象向内存一端移动,消除碎片。
优点:解决内存碎片问题,提升内存利用率。
适用场景:适合老年代,尤其是对象存活率高的场景。
4. 复制算法
graph TD
subgraph 复制算法
F[内存块A 使用中] -->|存活对象复制到内存块B| G[内存块B 激活]
G --> H[清空内存块A]
H -->|切换使用区域| F
style F fill:#fcc,stroke:#333
style G fill:#cfc,stroke:#333
end
原理:将内存分为大小相等的两块,每次只使用一块。垃圾回收时,将存活对象复制到另一块,并清空当前块。
优点:无碎片问题,分配效率高。
缺点:内存利用率仅 50%,不适用于大对象或高存活率场景。
适用场景:新生代(如 Eden 区与 Survivor 区的设计)。
二、主流的 Java 垃圾回收器(区分 JDK7 与 JDK8+)
1. JDK7 及之前的回收器
-
Serial 收集器:
- 单线程工作,适用于客户端应用或低配置环境;
- 新生代采用复制算法,老年代采用标记-压缩算法;
- 参数:
-XX:+UseSerialGC
。
-
Parallel 收集器(吞吐量优先) :
- 多线程并行回收,关注系统吞吐量;
- 新生代使用复制算法,老年代支持并行标记-压缩(
-XX:+UseParallelOldGC
); - 参数:
-XX:+UseParallelGC
。
-
CMS 收集器(低延迟优先) :
- 并发标记清除,减少停顿时间;
- 分为初始标记、并发标记、重新标记、并发清除四阶段;
- 缺点:内存碎片化,可能触发 Full GC;
- 参数:
-XX:+UseConcMarkSweepGC
。
2. JDK8 及之后的改进
G1 收集器(Garbage-First) :
1. 基本特性
- JDK9+ 默认收集器:取代 CMS,适用于现代大内存应用。
- 核心策略:优先回收垃圾最多的 Region(Garbage-First),最大化回收效率。
- 算法融合:结合 标记-压缩(老年代)与 复制算法(新生代),平衡吞吐量与低延迟。
- 启用参数:
-XX:+UseG1GC
2. 核心设计
设计特点 | 说明 |
---|---|
Region 划分 | 堆内存划分为多个等大小 Region(默认 1MB~32MB),独立回收,避免全堆扫描。 |
复制算法应用 | 存活对象少的 Region(如 Eden/Survivor)采用复制算法,无内存碎片。 |
标记-压缩应用 | 老年代 Region 执行压缩整理,解决碎片问题。 |
可预测停顿 | 通过 -XX:MaxGCPauseMillis 设定目标停顿时间(默认 200ms),软实时控制。 |
3. 工作流程
- 初始标记(STW) :短暂暂停,标记根直接引用对象。
- 并发标记:与用户线程并行,遍历对象图。
- 最终标记(STW) :修正并发阶段的引用变更。
- 筛选回收(STW) :按垃圾比例排序 Region,优先回收高垃圾 Region。
关键参数:
-XX:+UseG1GC # 启用G1
-XX:MaxGCPauseMillis=200 # 目标停顿时间(毫秒)
-XX:G1HeapRegionSize=4m # 设置Region大小(需为2的幂)
时序图
sequenceDiagram
participant UserThread as 用户线程
participant GCThread as GC线程
participant Heap as 堆内存(多个Region)
Note over GCThread,Heap: G1 收集器核心流程(兼顾低延迟与高吞吐)
rect rgba(200,200,255,0.1)
Note over GCThread: 初始标记(Initial Mark) - STW
GCThread->>Heap: 1. 扫描根节点直接引用对象
GCThread->>UserThread: 暂停用户线程(短暂)
GCThread->>Heap: 2. 标记存活对象(快速)
UserThread-->>GCThread: 恢复运行
end
rect rgba(200,255,200,0.1)
Note over GCThread: 并发标记(Concurrent Marking)
GCThread->>Heap: 3. 并发遍历对象图(与用户线程并行)
loop 并发标记
UserThread->>Heap: 用户线程修改对象引用
GCThread->>Heap: 增量更新标记状态(SATB算法)
end
end
rect rgba(255,200,200,0.1)
Note over GCThread: 最终标记(Remark) - STW
GCThread->>UserThread: 暂停用户线程(较短)
GCThread->>Heap: 4. 修正并发阶段变更的引用
GCThread->>Heap: 5. 标记最终存活对象
UserThread-->>GCThread: 恢复运行
end
rect rgba(255,255,200,0.1)
Note over GCThread: 筛选回收(Evacuation) - STW
GCThread->>Heap: 6. 统计Region垃圾比例(Garbage-First策略)
GCThread->>Heap: 7. 选择垃圾最多的Region优先回收
alt 复制算法应用
GCThread->>Heap: 复制存活对象到新Region(复制算法)
else 标记-压缩应用
GCThread->>Heap: 移动对象整理空间(标记-压缩算法)
end
GCThread->>Heap: 8. 清空旧Region(释放连续空间)
UserThread-->>GCThread: 恢复运行
end
Note over Heap: 结果:堆内存高效回收<br>(低停顿 + 高吞吐)
通过时序图可见,G1 通过分阶段并发操作和智能 Region 选择,在低延迟与高吞吐间取得了平衡。
与 CMS 的对比
特性 | G1 | CMS |
---|---|---|
内存模型 | Region 分块 | 连续分代(新生代/老年代) |
碎片处理 | 主动压缩(部分 Region) | 依赖 Full GC 压缩 |
停顿目标 | 可控(软实时) | 不可控 |
适用场景 | 大内存、低延迟 | 中小内存、低延迟 |
-
ZGC 与 Shenandoah(JDK11+ 实验性) :
- 目标是将停顿时间控制在 10ms 以内;
- 基于并发标记与压缩,适用于大内存场景。
三、GC 算法核心参数解析
1. 堆内存分配参数
-Xms
/-Xmx
:初始堆大小与最大堆大小(如-Xms512m -Xmx1024m
);-XX:NewRatio
:新生代与老年代的比例(默认 2,即老年代占 2/3);-XX:SurvivorRatio
:Eden 区与 Survivor 区的比例(默认 8:1:1)。
2. 回收器选择参数
- 串行回收:
-XX:+UseSerialGC
; - 并行回收:
-XX:+UseParallelGC
(新生代)、-XX:+UseParallelOldGC
(老年代); - CMS 回收:
-XX:+UseConcMarkSweepGC
,需配合-XX:CMSInitiatingOccupancyFraction
(触发阈值); - G1 回收:
-XX:+UseG1GC
,可调整-XX:MaxGCPauseMillis
(目标最大停顿时间)。
3. 调优辅助参数
-XX:+PrintGCDetails
:输出详细 GC 日志;-XX:+HeapDumpOnOutOfMemoryError
:内存溢出时生成堆转储文件;-XX:MaxTenuringThreshold
:对象晋升老年代的年龄阈值(默认 15)。