news 2026/8/16 17:39:55

记一次线上OOM排查:从“玄学“到定位的全过程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
记一次线上OOM排查:从“玄学“到定位的全过程

周五晚上十点半,手机开始疯狂震动。

运维群里甩出来一张监控截图——我们那个跑了大半年的订单服务,堆内存使用率从40%直接飙到98%,GC频率高得像抽风,Full GC一轮接一轮,每次回收完只能释放掉不到5%的内存。服务没挂,但接口响应时间从平时的30ms飙到了3秒以上,用户侧已经开始报超时了。

我当时的第一反应是:谁又上了什么新代码?

翻了一下最近的发布记录,最近一次上线是周三,改了一个批量导出Excel的功能。我当时心里就咯噔了一下——批量导出,大数据量,内存……这几个关键词凑在一起,大概率就是它。

但光靠猜没用,得看证据。


第一步:先止血

线上服务不能一直这么半死不活地挂着。先做了两件事:

  • 把批量导出的入口临时下掉,防止新的请求继续往内存里塞数据
  • 对服务做了一次滚动重启,先把内存释放掉,让服务恢复正常

重启之后内存果然降下来了,但我知道这只是治标。根因没找到,下次上线还可能炸。

第二步:拿到现场

好在我们之前配了JVM启动参数,开了OOM时自动dump:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heapdump/

但这次比较尴尬——服务还没真正抛OOM,只是GC压力巨大,堆还没完全打满。所以没有自动dump文件。

只能手动来。先登录机器,用jmap打一份堆快照:

jmap -dump:format=b,file=/data/logs/heapdump/manual_dump.hprof <pid>

这里有个坑要提一下:jmap在堆很大的时候会触发一次STW(Stop The World),当时服务本来就快挂了,这一锤子下去差点直接把它打死。所以生产环境打dump一定要小心,最好先把流量切走,或者在备节点上操作。

dump文件大概1.2G,scp拉到本地,用MAT(Eclipse Memory Analyzer)打开。

第三步:分析dump

MAT打开之后,第一眼看的是Dominator Tree(支配树),这个视图能直接告诉你哪些对象占了最多的内存。

结果很明显——排名第一的是一个byte[]数组,占了将近600MB。再往下看Histogram,大量的byte[]char[]对象,加起来占了堆的70%以上。

顺着引用链往上追(右键 → List Objects → with incoming references),最终定位到了一个类:

public class OrderExportService { public byte[] exportOrders(Date startTime, Date endTime) { List<Order> orders = orderMapper.selectByDateRange(startTime, endTime); // 问题就出在这里 ByteArrayOutputStream baos = new ByteArrayOutputStream(); Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("订单数据"); for (int i = 0; i < orders.size(); i++) { Row row = sheet.createRow(i); row.createCell(0).setCellValue(orders.get(i).getOrderNo()); row.createCell(1).setCellValue(orders.get(i).getAmount().toString()); // ... 还有十几个字段 } workbook.write(baos); return baos.toByteArray(); } }

问题一目了然:

  • selectByDateRange没有分页,时间范围一大,直接查出几十万条数据全塞进内存
  • XSSFWorkbook(xlsx格式)本身就是把整个文档加载到内存里的,不像SXSSFWorkbook支持流式写入
  • 最终baos.toByteArray()又复制了一份完整的byte数组

也就是说,一份导出数据在内存里至少存在了三份拷贝:原始查询结果List、Workbook对象、最终的byte数组。如果查出来10万条数据,每条数据序列化后大概1KB,那光数据就是100MB,乘以3就是300MB。再加上GC来不及回收,堆直接被打爆。

第四步:修复

改了两个地方:

1. 查询加分页,流式处理

public void exportOrders(Date startTime, Date endTime, OutputStream outputStream) { int pageSize = 5000; int pageNum = 1; SXSSFWorkbook workbook = new SXSSFWorkbook(500); // 只保留500行在内存 Sheet sheet = workbook.createSheet("订单数据"); int rowIndex = 0; while (true) { List<Order> orders = orderMapper.selectByDateRange( startTime, endTime, (pageNum - 1) * pageSize, pageSize ); if (orders.isEmpty()) break; for (Order order : orders) { Row row = sheet.createRow(rowIndex++); row.createCell(0).setCellValue(order.getOrderNo()); row.createCell(1).setCellValue(order.getAmount().toString()); } pageNum++; } workbook.write(outputStream); workbook.dispose(); // 清理临时文件 }

2. 不再把结果存成byte[]返回,而是直接写到response的OutputStream里

@GetMapping("/export") public void exportOrders(@RequestParam Date startTime, @RequestParam Date endTime, HttpServletResponse response) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=orders.xlsx"); orderExportService.exportOrders(startTime, endTime, response.getOutputStream()); }

这样改完之后,不管导出多少数据,内存中最多只保留5000条记录 + 500行的Workbook滑动窗口,内存占用基本是恒定的。

复盘和反思

回过头来看,这个问题其实不复杂,但有几个点值得注意:

1. 不要相信"数据量不大"的假设

开发的时候测试环境就那么几百条数据,怎么测都不会出问题。但线上用户一选就是三个月的时间范围,数据量直接翻了几百倍。做批量操作的时候,一定要考虑最坏情况。

2. POI的xlsx模式是个内存杀手

XSSFWorkbook底层用的是DOM模型,整个文档都在内存里。如果要导出大数据量的Excel,要么用SXSSFWorkbook(流式写入),要么考虑换成EasyExcel这类专门优化过大文件导出的库。

3. 监控告警要配好

这次是运维看到内存飙了才通知的,如果告警阈值设得合理(比如堆使用率超过80%就报警),能更早发现问题。我们后来把告警阈值从90%调到了80%,并且加了GC频率的告警。

4. 生产环境的dump操作要谨慎

jmap打dump会STW,堆越大影响越大。更好的做法是用jcmd或者配好自动dump参数,或者在预发环境复现问题后再dump。


写这篇主要是给自己做个记录,顺便也希望能帮到遇到类似问题的朋友。如果你也有过线上OOM的排查经历,欢迎在评论区聊聊你的故事,尤其是那些"排查到最后发现原因很离谱"的案例,我最喜欢听这种了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/16 17:33:48

当游戏资源被锁进.wz文件,你需要一把顺手的钥匙

当游戏资源被锁进.wz文件&#xff0c;你需要一把顺手的钥匙 【免费下载链接】Harepacker-resurrected All in one .wz file/map editor for MapleStory game files 项目地址: https://gitcode.com/gh_mirrors/ha/Harepacker-resurrected 你是否也有过这样的经历&#xf…

作者头像 李华
网站建设 2026/8/16 17:26:55

原神自动化工具BetterGI怎么用?把重复点击交给AI,每天省下半小时

原神自动化工具BetterGI怎么用&#xff1f;把重复点击交给AI&#xff0c;每天省下半小时 【免费下载链接】better-genshin-impact &#x1f4e6;BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | …

作者头像 李华
网站建设 2026/8/16 17:17:38

读懂数字化转型 | 数字化转型到底“转“什么?十大维度一次讲透

本文破除“数字化上系统”的认知偏差&#xff0c;点明转型本质是技术赋能的业务变革与价值链重构。结合零售行业迭代规律&#xff0c;从产品、服务、营销、组织等十大核心维度&#xff0c;全面拆解数字化转型的变革内核。一、痛点开篇&#xff1a;千万级投入&#xff0c;为何转…

作者头像 李华
网站建设 2026/8/16 17:12:03

零信任架构实战:基于天远人企关联构建自动化准入审查网关

破解分销商准入评估痛点&#xff1a;从线下材料收集到数据直连 在 B2B SaaS 供应链分销系统的建设与运营中&#xff0c;对下游分销商、加盟商的真实资质与关联关系进行合规确认是建立信任体系的基石。随着平台生态的扩展&#xff0c;传统的准入评估高度依赖线下提交大量工商资质…

作者头像 李华