尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

Java 明明有 GC,为什么还会 OOM?生产事故引起了一下反思
📅 发布时间:2026/6/18 21:23:08

有 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 就算再努力也救不了你。
真正的解决之道:不要一次抱太多。

相关新闻

  • 2026 年北京洋酒高价回收机构甄选:专业鉴定与高溢价变现行业参考 - 资讯纵览
  • Tortoise ORM:Python 异步世界的 Django 风格 ORM
  • 常州保时捷帕拉梅拉音响改装 音乐人生打造劲浪乌托邦打造移动音乐厅 - 音乐人生汽车音响

最新新闻

  • 2026北京市APP开发公司排名:高端定制服务商哪家好? - IT老炮老刘
  • 2026南通卫生间免砸砖防水、楼顶漏水、外墙渗水、地下室阳光房渗漏;正规防水补漏公司免费上门,线上质保,售后无忧。房屋漏水不再愁,24小时一站式快速维修。 - 企业资讯
  • Hy3preview:基于混元重建的多阶段解码头Agent模型
  • AI工具聚合平台:构建语义统一的本地化AI操作中枢
  • 雀魂数据分析终极神器:3步解锁你的麻将潜能提升秘籍
  • 深入解析8位MCU电机控制SDK:ADC缓冲模式、LED与开关驱动实战

日新闻

  • 2026年不锈钢卷板厂家推荐排行榜:冷轧热轧/304/201不锈钢卷板,高颜值耐腐蚀源头厂家实力精选 - 企业推荐官【官方】
  • FLUX.1-dev FP8模型实战指南:24GB以下显卡高效部署方案
  • 2026佛山长途搬家价目表:跨省跨市搬家费用完整计算指南 - 从来都是英雄出少年

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号