当前位置: 首页 > news >正文

一次 JVM Full GC 排查全过程

一、问题背景

某天下午,运维收到生产环境告警:某业务系统的定时任务服务 CPU 使用率飙升至 90%+,服务响应变慢,部分定时任务执行超时。

告警信息: [ALERT] xxx-schedule 服务 CPU 使用率 92.3% [ALERT] xxx-schedule 服务 Full GC 次数: 15次/分钟 [ALERT] syncDataJob 执行超时,耗时: 180s

二、问题现象

2.1 GC 日志分析

登录服务器查看 GC 日志:

tail -100f /logs/xxx-schedule/gc.log

2026-01-11T14:32:15.234+0800: [Full GC (Ergonomics) [PSYoungGen: 87296K->0K(153088K)] [ParOldGen: 349568K->298456K(349696K)] 436864K->298456K(502784K), [Metaspace: 128456K->128456K(1169408K)], 2.3456789 secs] 2026-01-11T14:32:18.123+0800: [Full GC (Ergonomics) [PSYoungGen: 87296K->0K(153088K)] [ParOldGen: 348234K->301234K(349696K)] 435530K->301234K(502784K), [Metaspace: 128456K->128456K(1169408K)], 2.5678901 secs] 2026-01-11T14:32:21.456+0800: [Full GC (Ergonomics) [PSYoungGen: 87296K->0K(153088K)] [ParOldGen: 349012K->305678K(349696K)] 436308K->305678K(502784K), [Metaspace: 128456K->128456K(1169408K)], 2.7890123 secs]

关键发现:

  • Full GC 频繁触发,约 3 秒一次
  • 老年代使用率持续在 85%+ (298456K/349696K)
  • 每次 Full GC 后老年代释放空间有限,呈上涨趋势
  • GC 耗时较长(2.3s ~ 2.8s)

2.2 使用 jstat 观察

jstat -gcutil 1000 10

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 98.23 45.67 86.34 94.12 91.23 1234 12.345 156 312.456 324.801 0.00 0.00 78.90 87.56 94.12 91.23 1235 12.456 157 315.234 327.690 0.00 97.45 12.34 88.91 94.12 91.23 1236 12.567 158 318.012 330.579

分析:

  • O (老年代) 持续增长:86% → 87% → 88%
  • FGC 次数快速增加
  • FGCT (Full GC 总时间) 占 GCT 的 96%+

三、定位过程

3.1 Dump 堆内存

# 生成堆转储文件 jmap -dump:format=b,file=/tmp/heap_dump_20260111.hprof

# 或者使用 jcmd(推荐) jcmd GC.heap_dump /tmp/heap_dump_20260111.hprof

3.2 使用 MAT 分析

导入 Eclipse MAT (Memory Analyzer Tool) 分析:

Leak Suspects 报告:

Problem Suspect 1:

The thread "xxl-job, JobThread-15-1704960000000" keeps local variables with total size 156,789,456 bytes (45.2% of total heap). Keywords: java.util.ArrayList, java.util.HashMap

Dominator Tree 分析:

3.3 追踪到具体代码

通过 MAT 的 “Path to GC Roots” 功能,定位到内存持有路径:

Thread: xxl-job, JobThread-15-1704960000000

└── SyncDataJob.handler() └── dataList (ArrayList) └── HashMap (156MB) └── Order objects (300,000+ 条)

四、根因分析

4.1 问题代码定位

查看 SyncDataJob.java:

@XxlJob(“syncDataJob”) public ReturnT handler(){

log.info("数据同步Job开始执行"); Long minId = 0L; while (true) { // 问题1: 每次查询100条,但内存中累积了所有处理过的数据 List<Map<String, Object>> dataList = orderMapper.selectByPage(minId, BATCH_SIZE); if (CollectionUtils.isEmpty(dataList)) { break; } // 问题2: 循环内创建大量临时对象 for (Map<String, Object> data : dataList) { Map<String, Object> map = new HashMap<>(); // 每条记录创建新Map // ... 填充数据 List<Map<String, Object>> paramList = new ArrayList<>(); paramList.add(map); // 调用外部服务 externalService.process(paramList); } // 更新minId继续下一批 minId = dataList.stream() .map(d -> ((Number) d.get("id")).longValue()) .max(Long::compareTo) .orElse(minId); } return ReturnT.SUCCESS;

}

4.2 问题分析

问题一:MyBatis 查询返回 Map 类型触发自定义 TypeHandler


项目配置了全局 TypeHandler: mybatis.type-handlers-package=com.xxx.domain.typehandler
JsonTypeHandler 会拦截 Map 类型,尝试将每个列值反序列化:
@MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandler {
private Map<String,String> map = new TreeMap<>(); // 每次实例化都创建 @Override public Map getNullableResult(ResultSet resultSet, String s) throws SQLException { return this.toObject(resultSet.getString(s), map.getClass()); // 频繁创建TreeMap }
}
问题二:循环内频繁创建临时对象
每处理一条记录就创建:

  • 1 个 HashMap (约 200 bytes)
  • 1 个 ArrayList (约 88 bytes)
  • 若干 String 对象

当数据量大时(如 30 万条),产生大量短生命周期对象,导致 Young GC 频繁,部分对象晋升到老年代。
问题三:数据量估算
– 查询符合条件的数据量 SELECT COUNT(1) FROM order_info WHERE status = 0; – 结果: 324,567 条
五、解决方案
5.1 修复 TypeHandler 冲突

5.2 优化内存使用

@XxlJob(“syncDataJob”) public ReturnT handler(){ log.info("数据同步Job开始执行"); Long minId = 0L; // 复用对象 Map<String, Object> map = new HashMap<>(16); List<Map<String, Object>> paramList = new ArrayList<>(1); while (true) { List<Map<String, Object>> dataList = orderMapper.selectByPage(minId, BATCH_SIZE); if (CollectionUtils.isEmpty(dataList)) { break; } for (Map<String, Object> data : dataList) { map.clear(); // 复用Map Long id = ((Number) data.get("id")).longValue(); minId = Math.max(minId, id); // 填充数据... map.put("orderId", data.get("orderId")); // ... paramList.clear(); paramList.add(map); externalService.process(paramList); } // 显式释放引用,帮助GC dataList.clear(); dataList = null; log.info("处理了一批数据,当前minId={}", minId); } return ReturnT.SUCCESS; }

5.3 JVM 参数调优

# 原参数 -Xms512m -Xmx512m -XX:+UseParallelGC # 优化后 -Xms1g -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8m -XX:InitiatingHeapOccupancyPercent=45 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/xxx-schedule/

六、效果验证

6.1 修复后 GC 情况

jstat -gcutil 1000 10

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 45.23 23.45 34.56 92.34 89.12 234 4.567 2 0.234 4.801 0.00 0.00 56.78 34.78 92.34 89.12 235 4.678 2 0.234 4.912 0.00 43.21 12.34 35.12 92.34 89.12 236 4.789 2 0.234 5.023

对比:

七、经验总结

7.1 排查流程

告警触发 → GC日志分析 → jstat观察 → heap dump → MAT分析 → 代码定位 → 修复验证

7.2 常见 Full GC 原因

  1. 内存泄漏:对象被长生命周期引用持有
  2. 大对象分配:直接进入老年代
  3. TypeHandler/序列化问题:隐式创建大量临时对象
  4. 批处理未分批:一次性加载过多数据
  5. MetaSpace 不足:类加载过多

7.3 预防措施

  1. 代码规范:批量处理必须分页,循环内避免频繁创建对象
  2. 监控告警:配置 GC 次数、老年代使用率告警
  3. 定期审查:review MyBatis resultType、TypeHandler 配置
  4. 压测验证:大数据量场景必须压测

7.4 常用排查命令速查

# 查看 GC 统计 jstat -gcutil 1000

# 查看堆内存使用 jmap -heap

# 生成堆转储 jcmd GC.heap_dump /tmp/dump.hprof

# 查看线程栈 jstack > /tmp/thread_dump.txt

# 查看类加载统计 jmap -histo | head -50

http://www.jsqmd.com/news/279132/

相关文章:

  • 2025年业界推荐:三集一体除湿热泵机组优质生产商口碑榜单,市面上可靠的三集一体除湿热泵机组公司口碑推荐榜普沃泰专注行业多年经验,口碑良好
  • 塑造2026年的六大软件开发与DevOps趋势
  • 聊聊口碑不错的AI应用技能培训机构哪家性价比高
  • **Apache Hadoop生态**构建,整合分布式存储、资源调度、计算引擎、数据管理、运维治理等全链路工具,提供从数据采集、存储、计算、分析到治理的端到端大数据处理能力
  • burpsuite 激活
  • 精选5家海外本地化营销推广服务商,助力外贸企业通过 Facebook、LinkedIn、TikTok 、INS、Google低成本营销推广高效获客
  • 2026年收银系统源码供应商推荐:银枣软件为何成为餐厅收银系统/奶茶店收银系统/餐饮管理系统首选
  • typescript 手动实现 Readonly
  • VMware Harbor 2.10.1 密码忘记重置
  • 高纯溶剂品牌深度测评:谁在质量、性价比与售后服务上更胜一筹?
  • 2222
  • 深度解析:西门子S7-200 SMART PLC控制16台三菱E740变频器的通讯程序
  • R语言读取CSV中文乱码自救手册(仅限内部流传):3个鲜为人知的编码调试技巧
  • Windows Server核心安全加固指南:十大企业级配置策略全面解析
  • PHP数组转JSON时中文变问号?5步精准定位并永久修复编码问题
  • 免费更新的进销存系统源码,源码开源可商用,带完整的搭建部署教程
  • 机器人五指灵巧手厂商推荐:从性能到服务的全面解析
  • 想系统入门CTF?这篇就够了:竞赛介绍、学习路线与核心刷题平台详解
  • Unity脚本生命周期函数顺序完全指南(含图解+执行优先级设置)
  • 【MySQL安全认证机制深度解析】:彻底搞懂Error 1045背后的密码验证逻辑
  • W-6D2:电磁炉、多头灶测温之选
  • 2026年盒马鲜生卡回收四种典型方式
  • 2026年广州PLC培训课程排行榜:广州万通PLC培训专业
  • 【Unity脚本生命周期深度解析】:C#中Awake、Start、Update执行顺序全揭秘
  • 高精度、耐腐蚀、可定制——玻璃转子流量计优质厂商全解析
  • 2026年普拉提教练培训机构哪家值得选择去哪学
  • [工程实战] 攻克“资料孤岛”:基于隐语纵向联邦学习的金融风控建模全解析
  • 总结浙江地区口碑好的成人自考培训机构,春华教育排名如何?
  • 吴忠市英语雅思培训辅导机构推荐、2026权威出国雅思课程中心学校口碑排行榜
  • 分享专业生产菱形钢板网厂家,中盛制网口碑怎么样?