目录
引言:为什么GC问题如此重要?
想象一下,你精心开发的Java应用在生产环境运行良好,突然有一天,用户开始抱怨系统变慢、响应延迟,甚至出现可怕的"OutOfMemoryError"错误。这很可能就是GC(垃圾回收)问题在作祟。作为Java开发者,理解并解决GC问题是必会技能。
一、GC基础
1.1 什么是垃圾回收?
垃圾回收是JVM自动管理内存的机制,它负责:
分配内存给新对象
识别哪些对象不再被使用
回收这些对象占用的内存
1.2 分代收集理论
JVM将堆内存分为几个区域,采用不同的回收策略:
区域 |
对象特点 |
回收算法 |
回收频率 |
---|---|---|---|
新生代(Eden+Survivor) |
新创建的对象 |
复制算法 |
高 |
老年代(Tenured) |
存活时间长的对象 |
标记-清除/整理 |
低 |
元空间(Metaspace) |
类元数据 |
- |
很低 |
1.3 常见GC收集器
Serial GC:单线程,适合客户端应用
Parallel GC:多线程,吞吐量优先
CMS:并发标记清除,减少停顿时间
G1:面向服务端,平衡吞吐量和停顿
ZGC:超低延迟,JDK11+引入
Shenandoah:低停顿,RedHat贡献
二、识别GC问题
2.1 GC问题的常见症状
应用响应变慢:频繁GC导致应用线程暂停
CPU使用率高:GC线程占用大量CPU资源
内存占用高:对象无法被回收
OutOfMemoryError:内存耗尽
2.2 诊断工具
# 查看JVM参数和内存使用
jps -v
jstat -gcutil <pid> 1000 10 # 每1秒采样一次,共10次
# 堆内存分析
jmap -heap <pid>
jmap -histo:live <pid> | head -20 # 查看对象分布
# 生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>
可视化工具
VisualVM:JDK自带,功能全面
JConsole:简单监控
Eclipse MAT:内存分析神器
GCViewer:分析GC日志
Arthas:阿里开源,生产环境诊断
三、常见GC问题及解决方案
3.1 频繁Full GC
症状:老年代频繁回收,应用停顿明显
原因:
对象过早晋升到老年代
老年代空间不足
内存泄漏
解决方案:
调整新生代大小:
-Xmn512m # 设置新生代大小为512MB
调整晋升阈值:
-XX:MaxTenuringThreshold=8 # 提高晋升到老年代的年龄
使用合适的收集器:
-XX:+UseG1GC # 使用G1收集器
3.2 内存泄漏
症状:内存使用持续增长,Full GC后回收很少
诊断步骤:
- 获取堆转储文件
- 使用MAT分析对象引用链
- 找出异常增长的对象
常见泄漏点:
静态集合类
未关闭的资源(文件、连接)
监听器未注销
线程池未清理
示例代码:
// 错误示例:静态Map导致内存泄漏
public class Cache {
private static final Map<String, Object> CACHE = new HashMap<>();
public static void put(String key, Object value) {
CACHE.put(key, value);
}
// 缺少清除方法
}
// 正确做法:使用WeakHashMap或定期清理
public class SafeCache {
private static final Map<String, Object> CACHE = new WeakHashMap<>();
public static void clean() {
CACHE.clear();
}
}
3.3 GC停顿时间过长
症状:应用出现明显卡顿,GC日志显示单次停顿超过1秒
优化方案:
切换低延迟收集器:
-XX:+UseZGC # JDK11+
-XX:+UseShenandoahGC # JDK12+
调整G1参数:
-XX:MaxGCPauseMillis=200 # 设置目标停顿时间
-XX:G1NewSizePercent=30 # 新生代最小占比
-XX:G1MaxNewSizePercent=60 # 新生代最大占比
减少对象分配:
避免在循环中创建临时对象
使用对象池
3.4 元空间溢出
症状:Metaspace
持续增长,抛出OutOfMemoryError: Metaspace
解决方案:
增大元空间:
-XX:MaxMetaspaceSize=512m
控制类加载:
检查是否有重复加载的类
避免动态生成过多类
使用ClassLoader
泄漏检测工具
记住,过早优化是万恶之源。在遇到实际GC问题前,使用JVM默认配置通常是最佳选择。当问题出现时,按照"监控-分析-调整-验证"的循环进行调优,你一定能成为GC调优的高手!