一、Full GC为什么让人头疼
每次系统卡顿的时候,十有八九是Full GC在搞鬼。想象一下,你正在线上系统处理重要业务,突然监控告警疯狂提示"Full GC频率过高",这时候你的心情大概和看到早高峰地铁挤满人一样绝望。Full GC就像个霸道总裁,一旦它开始工作,整个JVM都得停下来等它,这就是所谓的"Stop-The-World"。
举个例子,我们有个电商系统,大促时频繁出现卡顿。用jstat一看,好家伙,Full GC每5分钟就来一次,每次停顿2秒以上。这就像收银台每隔五分钟就要关门盘点,顾客不投诉才怪。
二、揪出Full GC的罪魁祸首
要解决问题,先得知道问题出在哪。常见的Full GC诱因主要有三个:
- 老年代空间不足
- 永久代/元空间撑爆了
- System.gc()被乱调用
先看个内存泄漏的典型案例(示例基于Java 8):
// 错误示例:静态Map不断增长导致内存泄漏
public class ShoppingCartManager {
private static Map<Long, List<Product>> userCarts = new HashMap<>();
public void addToCart(long userId, Product product) {
// 用户购物车数据永远不清理,最终撑爆老年代
userCarts.computeIfAbsent(userId, k -> new ArrayList<>()).add(product);
}
// 正确做法应该增加清理机制
public void clearCart(long userId) {
userCarts.remove(userId);
}
}
用jmap分析堆dump,你会发现老年代堆满了ShoppingCart对象。这就是典型的内存泄漏,对象该释放的时候没释放。
三、调优实战:对症下药
3.1 调整堆大小
堆太小会导致频繁GC,太大又会导致单次GC停顿时间长。建议通过以下步骤确定合理值:
- 用GC日志分析当前使用情况:
java -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar yourApp.jar - 观察老年代使用峰值
- 设置初始(-Xms)和最大(-Xmx)堆大小为峰值的1.5倍
3.2 选择合适的GC算法
不同场景适合不同的GC算法。比如对于低延迟要求的系统,可以用G1:
# 使用G1 GC的推荐配置
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=45 \
-jar yourApp.jar
这个配置告诉JVM:
- 使用G1收集器
- 目标停顿时间200ms
- 堆使用率达到45%时启动并发标记
3.3 元空间调优
如果问题是Metaspace撑爆了,可以这样调整:
# 限制元空间大小并开启压缩指针
java -XX:MaxMetaspaceSize=256m \
-XX:+UseCompressedClassPointers \
-jar yourApp.jar
四、避坑指南
不要随便调用System.gc()
很多第三方库会偷偷调用它,可以通过参数禁用:-XX:+DisableExplicitGC注意大对象分配
比如一次性加载大文件到内存:// 错误示例:一次性读取大文件 byte[] fileData = Files.readAllBytes(Paths.get("huge_file.bin")); // 正确做法:使用流式处理 try (InputStream is = Files.newInputStream(Paths.get("huge_file.bin"))) { // 分块处理数据 }合理设置对象年龄阈值
默认15次Young GC后对象会晋升到老年代,对于短命对象可以降低这个值:-XX:MaxTenuringThreshold=5
五、监控与持续优化
调优不是一劳永逸的事,需要持续监控。推荐配置:
开启GC日志详细记录:
-XX:+PrintGCApplicationStoppedTime -XX:+PrintPromotionFailure -XX:+PrintTenuringDistribution使用JMX监控关键指标:
// 获取GC次数和耗时 GarbageCollectorMXBean gcBean = ManagementFactory.getGarbageCollectorMXBeans().get(0); System.out.println("GC次数:" + gcBean.getCollectionCount()); System.out.println("GC耗时:" + gcBean.getCollectionTime() + "ms");
六、总结
Full GC问题就像慢性病,需要定期体检(监控)、对症下药(调优)、养成良好的生活习惯(编码规范)。记住几个关键点:
- 先测量再优化,没有数据支撑的调优都是耍流氓
- 根据应用特性选择合适的GC算法
- 合理设置堆大小,不是越大越好
- 避免内存泄漏和大对象分配
- 持续监控,动态调整
调优是个系统工程,需要结合具体业务场景反复试验。希望这些经验能帮你少走弯路,让你的JVM跑得比兔子还快!
评论