一、问题背景
某天线上服务突然出现异常:
- CPU 持续飙升至 100%
- 页面无法访问
- 最终服务崩溃
- 重启后恢复正常
日志中出现大量异常:
1 | java.lang.OutOfMemoryError: Java heap space |
JVM 参数如下:
1 | -Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/heapError -jar /home/pms-server.jar --spring.profiles.active=prod --server.port=48080 |
二、现象分析
通过监控数据可以观察到:
1️⃣ CPU 异常提前出现
- 12:57 开始 CPU 持续升高
- 内存使用率看似稳定
👉 说明:不是简单“内存瞬间打满”,而是GC 风暴,因为查询出来海量数据赋值给变量,即将达到512M,于是GC 开始频繁触发,内存不会被占满,但是CPU 会持续升高。
2️⃣ 网络流量异常
12:56 开始,下行流量暴涨(10MB/s+)
👉 说明: 服务正在返回超大响应数据
3️⃣ OOM 时间点
13:11 出现 OutOfMemoryError
👉 说明: JVM 在 12:57 ~ 13:11 之间经历了持续“内存挣扎”
三、关键排查手段
本次排查主要依赖两个核心工具:
✅ 1. 请求日志(infra_api_access_log表)
定位时间窗口(根据begin_time,而非create_time, 因为create_time 是请求处理完成的时间):
1 | 12:50 ~ 12:57 |
发现异常请求:
1 | GET /admin-api/hotel/price/dateList |
这个请求2026-03-24 12:55:44 开始,2026-03-24 13:13:27 结束,最终报错。
✅ 2. 堆转储文件(heap dump)
因为已经重启了服务,无法直接分析内存溢出的现场,只能通过它的尸体——堆转储文件来分析。
通过 IDEA 打开 heapError.hprof 文件,重点分析:

发现:
1 | DatePriceRespVO Retained Size ≈ 141MB |
👉 直接锁定问题对象和请求入口
四、问题根因
🎯 核心问题:dateList 接口返回数据过大
请求参数:
1 | { |
🚨 致命问题点:
1. 时间范围异常巨大
1 | checkinTime: 1774332000000 |
👉 时间跨度:极大(远超正常业务范围)
2. JVM 堆限制
1 | -Xmx512m |
👉 实际可用空间:
- 老年代 + 新生代 + 线程等
- 可用业务内存远小于 512M
五、问题本质
👉 本次 OOM 本质是: 接口未限制查询范围,导致超大集合对象生成,引发 GC 风暴,最终 OOM
完整链路:
1 | 超大时间范围请求 |
六、解决方案
✅ 1. 强制限制时间范围(必须)
1 | if (days > 30) { |
✅ 2. 接口分页 / 分段查询
❌ 错误方式:一次返回全年价格
✅ 正确方式:按月 / 按周查询
✅ 3. 精简返回字段
1 | DatePriceRespVO → 精简DTO |
避免:嵌套对象 / 冗余字段
✅ 4. 增加接口保护
- 限流(Rate Limit)
- 参数校验
- 最大返回条数限制
✅ 5. JVM 优化(辅助)
1 | -Xms2g |
⚠️ 注意:这只是缓解,不是根治
七、经验总结
🎯 1. “全量查询接口”是 OOM 高发区
典型危险命名:
1 | getAll |
🎯 2. 时间维度是隐形杀手
1 | 1天 → 安全 |
🎯 3. CPU 高 ≠ 计算多
很多时候: CPU 高 = GC 在拼命救场
🎯 4. 排查三板斧
1 | GC日志 → 定时间 |
八、常见的 OOM 原因
🎯 1. 大集合 / 全量查询(这次就是典型)
表现
接口名:
getAll / listAll / dateList返回:List 很大(几十万条)
本质
1 | 查询范围无边界 → 数据量指数增长 → 内存被吃光 |
典型代码:
1 | List<Room> list = roomMapper.selectList(null); |
或:
1 | for (LocalDate d = start; d <= end; d++) { |
🔥 特征
heap dump 中:ArrayList / HashMap 占用巨大
GC日志:
- Full GC频繁
- old区回收不掉
🎯 2. 对象嵌套过深(数据结构设计问题)
表现
1 | Room { |
👉 一次请求返回:ArrayList / HashMap 占用巨大
🔥 特征
单个对象不大,但层级展开后爆炸
heap dump:DTO嵌套List
🎯 3. JSON 序列化放大
表现
1 | 对象 → JSON字符串 |
👉 内存占用:对象 + JSON = 2倍甚至更多
🔥 特征
heap dump:
char[] / byte[] 占用巨大网络流量暴涨(你这次就有)
🎯 4. 缓存使用不当(常见坑)
表现
1 | Map<String, Object> cache = new HashMap<>(); |
👉 没有:
过期策略
大小限制
🔥 特征
heap dump: HashMap 占用巨大
GC回收不了(强引用)
🎯 5. 内存泄漏(对象无法释放)
表现
对象不再使用,但仍被引用
常见场景
1 | // 请求结束后,list也不会被回收 |
或:
1 | // ThreadLocal 未清理 |
🔥 特征
Old区持续增长
Full GC 后仍不下降
🎯 6. 大文件 / 大对象处理
表现
1 | byte[] bytes = file.readAllBytes(); |
或:
1 | 一次性加载大Excel / 大图片 |
🔥 特征
- heap dump:
byte[] 巨大
🎯 7. 线程过多(间接OOM)
表现
1 | new Thread(...).start(); |
或线程池无限制
🔥 特征
线程数暴涨
OOM类型:
unable to create new native thread
🎯 8. JVM参数设置过小
表现
1 | -Xmx512m |
👉 正常业务也可能撑不住
🔥 特征
数据量不大也OOM
提高内存后明显改善
🎯 9. 频繁创建对象(GC风暴)
表现
1 | for (...) { |
👉 对象短命但数量极多
🔥 特征
CPU 100%
内存看起来不高(你这次就是)
🎯 10. 第三方组件问题
常见
Redis 客户端(如 Redisson)
ORM(MyBatis / Hibernate)
JSON库