news 2026/6/18 21:22:11

Java 明明有 GC,为什么还会 OOM?生产事故引起了一下反思

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 明明有 GC,为什么还会 OOM?生产事故引起了一下反思

有 GC,为什么还会 OOM?这么问好像略显白痴一些

一句话答案

GC 只能回收没人用的对象
如果对象一直有人拿着引用不放,GC 永远不敢动它,内存就会撑爆。


二、用生活场景理解

把 JVM 堆内存想象成一个停车场,GC 是停车场管理员

停车场(Heap 堆内存) ┌─────────────────────────────────────┐ │ 🚗 车A(有人在用) │ │ 🚗 车B(有人在用) │ │ 🚗 车C(没人用,但钥匙还插着) │ ← GC 不敢拖走! │ 🚗 车D(有人在用) │ │ 🚗 车E(有人在用) │ │ 🚗 车F(有人在用) │ │ 🚗 车G(有人在用) │ │ ....(停满了) │ └─────────────────────────────────────┘

GC 的工作原则:只要有一把"钥匙"(引用)指向这辆车,我就不能拖走它。

OOM 发生的原因:停车场停满了,新车进不来,但所有车都"有钥匙",管理员一辆都不能拖走。

停车场满了,新车来了 →java.lang.OutOfMemoryError: Java heap space


三、那 GC 到底在干什么?

GC 会定期扫描,把真正没人用的对象清掉:

GC 扫描: main() ─────▶ List list ─────▶ [obj1, obj2, obj3] │ ← 可以从 main 追踪到,还活着,不回收 ──┘ ╔══════════════════════════════╗ ║ 找不到任何路径能追踪到的对象 ║ ← GC 回收这些 ╚══════════════════════════════╝

关键词:“可达性”。只要从程序入口能追踪到这个对象,GC 就认为它"还活着",绝对不回收。


四、常见的 OOM 真实原因

原因 1:一次加载数据太多(最常见)

// 我们项目的 OOM 就是这个!List<Map<String,Object>>saleList=salesDataGateway.batchSelectMap(query);// pageSize = 10000,每条 SalesData 有几十个字段// 10000 条 × 5KB/条 = 50MB 在堆里// 然后 bulk() 把它序列化成 byte[],又占 50MB// 峰值内存 = 50MB × 3(对象 + 序列化 + 网络缓冲)= 150MB// 这批数据还没处理完,下一批又进来了// 内存越堆越多 → OOM

类比:你让搬运工一次搬 10000 箱货,他抱不动,直接跪倒。


原因 2:List / Map 无限增长(内存泄漏)

// 全局静态的 Map,往里加东西,从不清理staticMap<String,Object>cache=newHashMap<>();voidprocess(Requestreq){cache.put(req.getId(),req.getData());// 一直加// 永远没有 remove}// GC 看到 cache 还活着(静态变量) → 永远不回收// cache 里的东西越来越多 → OOM

类比:仓库(HashMap)一直进货,从不出货,终于放不下了。


原因 3:循环里不断创建大对象

for(inti=0;i<1000000;i++){byte[]data=newbyte[1024*1024];// 每次创建 1MB 的数组process(data);// 以为 data 用完就没了// 但 GC 来不及回收!// 循环太快,内存创建速度 >> GC 回收速度 → OOM}

类比:工厂每秒生产 1000 个箱子,但清理工每秒只能处理 100 个,堆积越来越多,仓库炸了。


原因 4:字符串拼接(大报文场景)

Stringresult="";for(Stringline:millionLines){result=result+line;// ❌ 每次都创建新字符串对象!}// 前一个 result 虽然没人用了,但 GC 还没来得及回收// 新的已经创建出来,内存翻倍 → OOM

正确做法:StringBuilder,原地拼接,不产生中间对象。


原因 5:ByteArrayOutputStream 无限扩容

// 我们 ES 写入 OOM 的直接原因// RestHighLevelClient.bulk() 内部:ByteArrayOutputStreamout=newByteArrayOutputStream();for(IndexRequestreq:requests){byte[]json=serialize(req);// 把每个文档序列化out.write(json);// 往 ByteArrayOutputStream 里写}// ByteArrayOutputStream 内部是 byte[]// 写满了就 Arrays.copyOf 扩容(扩成原来的 2 倍!)// 10000 条数据:50MB → 扩容 → 100MB → 扩容 → 200MB → OOM

类比:快递公司把所有快递都装进一个袋子再发出去,袋子越撑越大,最后裂开了。


五、GC 为什么来不及救场?

GC 不是随时都在工作的,它有触发条件:

内存分配时序: 程序申请内存 │ ▼ Eden 区(年轻代)满了? │ ▼ 是 触发 Minor GC(清理年轻代) │ 还不够?Old Gen(老年代)满了? │ ▼ 是 触发 Full GC(清理全部) │ Full GC 后还不够? │ ▼ 是 OOM !!!

关键矛盾:

  • 程序申请内存速度:很快(循环 + 批量操作)
  • GC 回收速度:相对慢(需要 STW 停顿,有开销)

当申请速度 >> 回收速度,就算 GC 拼命跑,也追不上。


六、为什么 GC 不回收"正在用的"对象?

这是 GC 的安全保证:

假设 GC 强行回收正在用的对象: Thread A: list.get(0).getName() ↑ GC 突然把这个对象回收了 ↑ Thread A: NullPointerException 崩溃! 所以 GC 宁可 OOM,也不会回收有引用指向的对象。 这是 Java 内存安全的基础。

七、OOM 的本质总结

OOM 本质 = 内存需求 > 可用内存 两种情况: 情况 1:真的用了太多内存(一次批量太大) 解决:减少批量大小、流式处理、分批写入 情况 2:内存泄漏(该释放的没释放) 解决:检查静态集合、检查缓存是否有上限、 用 WeakReference、及时 close 资源 GC 能做的: ✅ 自动回收"没有引用"的对象 ❌ 不能回收"有引用但逻辑上不用了"的对象 ❌ 不能阻止程序一次申请过多内存

八、我们项目 OOM 的具体原因和修法

原因链: batchSelectMap 查 10000 条 │ 50MB List<Map> ▼ bulk() 序列化所有数据到 ByteArrayOutputStream │ ByteArrayOutputStream 扩容 → 50MB → 100MB → 200MB ▼ HTTP 发送(还要序列化一遍) │ 再占一份内存 ▼ OOM !!! GC 想回收,但上面每一步的对象都"有人拿着",没法回收。 等到 bulk() 执行完,GC 才能回收,但那时已经 OOM 了。 修法 1:减小 pageSize(治标) 10000 → 2000,峰值内存直接降 5 倍 修法 2:BulkProcessor(治本) 每 500 条 / 每 2MB 自动 flush 一次 flush 完这批,对象释放,GC 及时回收 下一批再来时内存已经空出来了 峰值内存始终控制在 2MB 级别

九、让我们记住这一句话

GC 是清道夫,但它只清"没人要的垃圾"。
如果你的代码一直"抱着"数据不放,GC 就算再努力也救不了你。
真正的解决之道:不要一次抱太多。

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

Java计算机毕设之基于 Spring Boot 的林区土地资源管控系统的设计与实现 基于 Spring Boot 的林业资源数据统计分析系统(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/18 21:19:13

【毕业设计】基于 Java 的用户情绪记录与宣泄服务系统的设计与实现 基于 Java 的轻量化心理情绪宣泄互动平台(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/18 21:17:15

毕设开源项目合集|SpringBoot+Vue 全套源码免费下载,适配课程设计 / 毕业设计(毕设论文智能AI画图助手)

还在为毕业设计找不到完整可运行的项目源码发愁&#xff1f;网上零散代码残缺、前后端不分离、缺少配套论文、调试踩坑没人带&#xff1f;今天给大家整理一批高质量完整开源毕设项目&#xff0c;全部前后端分离、附带数据库脚本&#xff0c;直接可二次开发&#xff0c;文末附官…

作者头像 李华
网站建设 2026/6/18 21:10:16

投入式液位变送器LTJ31-10000/61-LH-T22

投入式液位变送器LTJ31-10000/61-LH-T22投入式液位变送器LTJ31-10000/61-LH-T22LTJ31投入式液位变送器是基于所测液体静压与该液体高度成正比的原理&#xff0c;投入式液位变送器采用扩散硅或陶瓷敏感元件的压阻效应&#xff0c;将静压转成电信号。经过温度补偿和线性校正。转换…

作者头像 李华
网站建设 2026/6/18 21:10:06

Django毕设选题推荐:基于 Django+Vue 的智慧农业数据可视化系统的设计与实现 基于 Django+Vue 的农业环境监测与管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/18 21:09:31

助睿Max数据大屏实战:从零搭建浏览器用户画像分析系统

一、实验背景在产品运营与数据分析工作中&#xff0c;我们经常需要回答一个核心问题&#xff1a;"谁在用我们的产品&#xff1f;"用户画像分析的价值在于将抽象的人口属性&#xff08;性别、年龄、职业、收入、地域等&#xff09;转化为可直观理解、可支撑决策的人群…

作者头像 李华