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

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

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

相关文章:

  • 【测试必修】通过charles修改接口返回数据
  • 人件阅读笔记03
  • 制造业数字化成熟度: 企业完成数字化转型之后, 下一步任务是什么?
  • 2026有实力的品牌控价公司TOP3盘点 - 资讯纵览
  • 机器人--robotstudio查找机械臂配置
  • AI-Agent工业级训练全链路解析:从基座模型到生产沙箱
  • 2026年宜宾买手机靠谱门店排行:品牌授权商家大盘点 - 资讯纵览
  • 2026年济南GEO优化公司推荐排行榜|企业AI搜索曝光选型全指南 - 资讯纵览
  • 我用 Docker 部署了一套完整的 AI 应用:从本地开发到云端上线,全流程踩坑记录
  • 江北黄金回收全攻略:6家好店、4种渠道、5条忠告,一篇全搞定 - 宁波早知道
  • Tortoise ORM:Python 异步世界的 Django 风格 ORM
  • 2026 年北京洋酒高价回收机构甄选:专业鉴定与高溢价变现行业参考 - 资讯纵览
  • 常州保时捷帕拉梅拉音响改装 音乐人生打造劲浪乌托邦打造移动音乐厅 - 音乐人生汽车音响
  • 从同质化内卷到差异化突围!Qi认证构筑产品核心竞争力
  • 三分钟上手LuckyLilliaBot:多协议QQ机器人搭建全攻略
  • 2026专业的天津全屋定制源头服务商TOP3 - 信息热点
  • 工会端午节发放福利方案
  • 重庆燃气安全设备哪家强?五大品牌全维度深度测评 - 资讯纵览
  • 公司发的京东E卡怎么换钱?2026京东E卡回收攻略(附回收价格、变现流程、避坑指南) - 资讯纵览
  • 024、ONNX作为算子中间表示的优缺点分析
  • 天津高端全屋定制高性价比工厂指南 省钱又靠谱的选择 - 信息热点
  • 靠谱的北京高端全屋定制工厂推荐:7条必查筛选标准 - 信息热点
  • 2026年投标资质办理服务平台实测口碑排行:10家平台资质、通过率、服务全维度对比 - 互联网科技品牌测评
  • 2026天津4家热门全屋定制源头工厂测评 - 信息热点
  • 6种字重的苹方字体:跨平台设计开发的专业解决方案
  • Seedream 2.0深度解析:中文文生图的工程化破局之道
  • 助睿Max数据大屏实战:从零搭建浏览器用户画像分析系统
  • 图片怎么改成指定宽高像素?用秒转工具箱小程序就能调 - 效率工具研究所
  • 【对比】测评系列:又测了 5 个酒店/机票 API 服务
  • 2026天津靠谱全屋定制源头厂家推荐清单 - 信息热点