如何解决 OutOfMemoryError 内存溢出 —— 原因、定位与解决方案

网罗开发(小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


文章目录

    • 摘要
    • 先把症状搞清楚 — OOM 常见表现
    • 简单定位思路(快速排查步骤)
    • 瞬间分配大对象导致 OOM
      • 源码 OOMAllocate.java
      • 编译与运行(在终端)
    • 内存泄漏模拟(静态集合持续增长)
      • 源码 LeakExample.java
      • 编译与运行
    • 调试与定位工具(实用命令与说明)
      • 生成堆转储(heap dump)
      • 快速统计类实例(堆直方图)
      • 在线分析
      • GC 日志(定位 GC 问题)
    • 常见解决策略(按场景给建议)
      • 快速应急(治标)
      • 根本修复(治本)
      • 特殊类型 OOM 的处理
    • 实战技巧与最佳实践(工程化建议)
    • 常见问答(QA)
    • 总结

摘要

Java 程序出现 OutOfMemoryError(OOM)是常见且恼人的问题。它可能是 JVM 堆不足、内存泄漏、或者本地/直接内存耗尽引起的。本文用通俗的语言解释 OOM 的常见类型、如何快速定位(命令与工具)、以及 2 个可运行的 Demo(一个“瞬间分配大对象”触发 OOM,一个“内存泄漏”模拟)来复现和验证问题,并给出实际修复建议与最佳实践。

先把症状搞清楚 — OOM 常见表现

当程序遇到 OOM,常见异常信息有:

  • java.lang.OutOfMemoryError: Java heap space(堆内存用尽)
  • java.lang.OutOfMemoryError: GC overhead limit exceeded(GC 占比过高)
  • java.lang.OutOfMemoryError: Metaspace(元空间/类元数据用尽)
  • java.lang.OutOfMemoryError: Direct buffer memory(直接内存 / native buffer 用尽)
  • 有时伴随未生成堆转储(如果没开 -XX:+HeapDumpOnOutOfMemoryError

出现 OOM 时 JVM 往往会打印堆栈并退出。定位问题的第一步是判断是哪种 OOM(heap / metaspace / direct / native)。

简单定位思路(快速排查步骤)

  1. 确认 OOM 类型:查看异常消息(heap / metaspace / direct 等)。
  2. 复现场景:能否用小堆内存复现(-Xmx64m)?如果可以,说明问题容易触发。
  3. 抓堆快照(Heap Dump):在运行时或 OOM 时生成 hprof(参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof)。
  4. 查看类实例分布jcmd <pid> GC.class_histogram > histo.txtjmap -histo:live <pid>
  5. 用可视化工具分析:jvisualvm、Eclipse MAT(Memory Analyzer)打开 heap.hprof 找顶级占用对象和 GC Roots。
  6. 考虑 GC / 参数问题:有时候是堆太小,简单增大 -Xmx 就能缓解,但这只是治标。要找出为何占用如此多。

瞬间分配大对象导致 OOM

这个 Demo 用来展示“把很大的数组一次性分配”导致 OOM 的情形,方便你通过减小堆内存复现并观察。

源码 OOMAllocate.java

// 保存为 OOMAllocate.java
public class OOMAllocate {public static void main(String[] args) throws InterruptedException {System.out.println("PID: " + ProcessHandle.current().pid());// 等待几秒,方便 attach 工具(jvisualvm)Thread.sleep(5000);try {// 分配一个巨大的对象,触发 OOMint size = 200_000_000; // 2e8 -> 大约 800MB for int[]System.out.println("Allocating int[" + size + "]");int[] arr = new int[size];System.out.println("Allocated: " + arr.length);} catch (Throwable t) {t.printStackTrace();}// 保持进程不退出,便于分析Thread.sleep(60_000);}
}

编译与运行(在终端)

# 编译
javac OOMAllocate.java# 运行:限制堆为 128MB 并在 OOM 时生成 heap dump
java -Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_OOMAllocate.hprof OOMAllocate

预期结果:程序会在分配 int[] 时抛出 OutOfMemoryError: Java heap space,并在当前目录生成 heap_OOMAllocate.hprof

分析

  • 这个 Demo 说明“瞬时大对象分配”在堆较小时非常容易触发 OOM。
  • 观察堆直方图(jmap -histo)和 heap dump 可看到大对象占比。

内存泄漏模拟(静态集合持续增长)

这个 Demo 模拟常见的内存泄漏:把对象不停放入静态集合且不释放(例如缓存或 List 没有限制),最终导致堆耗尽。

源码 LeakExample.java

// 保存为 LeakExample.java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class LeakExample {static class Holder {// 占大内存的 payloadprivate byte[] payload;public Holder(int mb) {this.payload = new byte[mb * 1024 * 1024];}}// 静态 List 模拟缓存/泄漏private static final List<Holder> leakingList = new ArrayList<>();public static void main(String[] args) throws Exception {System.out.println("PID: " + ProcessHandle.current().pid());int mb = 1;if (args.length > 0) {mb = Integer.parseInt(args[0]);}int count = 0;try {while (true) {leakingList.add(new Holder(mb)); // 每次分配 mb MB 并保留引用count++;if (count % 10 == 0) {System.out.println("Allocated blocks: " + count + ", total approx MB: " + (count * mb));}Thread.sleep(200);}} catch (OutOfMemoryError oom) {oom.printStackTrace();System.out.println("OOM after allocating blocks: " + count);// 触发堆转储如果配置了 -XX:+HeapDumpOnOutOfMemoryError}}
}

编译与运行

javac LeakExample.java# 用较小堆触发 OOM,如 64MB
java -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_LeakExample.hprof LeakExample 1

程序会持续分配 1MB 块并保存在静态 List,最终触发 OutOfMemoryError。生成 heap dump 后,你可以用 jvisualvm 或 Eclipse MAT 打开 heap_LeakExample.hprof

分析思路

  • jvisualvm 连接进程,查看 heap 使用趋势;
  • jcmd <pid> GC.class_histogramjmap -histo:live <pid> 查看哪些类占用最多(很可能是 byte[]LeakExample$Holder);
  • 在 MAT 中查看 GC Roots,找到导致持有对象的路径(通常是静态变量)。

调试与定位工具(实用命令与说明)

生成堆转储(heap dump)

在运行 Java 时加入:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heap.hprof

或者在运行时触发:

jcmd <pid> GC.heap_dump /tmp/heap.hprof

快速统计类实例(堆直方图)

# 使用 jcmd(推荐)
jcmd <pid> GC.class_histogram > histo.txt# 或者 jmap
jmap -histo:live <pid> > histo_jmap.txt

这会列出每个类的实例数量与占用字节,帮助定位占内存最多的类。

在线分析

  • jvisualvm(JDK 自带或独立下载):界面化查看堆占用、线程、profiling、GC 等,能生成堆快照并查看对象占用情况。
  • Eclipse MAT (Memory Analyzer):专业的 heap.hprof 分析工具,能找出“泄漏嫌疑人”(suspects)并生成 Leak Suspects 报表。
  • Java Flight Recorder / Mission Control(JFR/JMC):更高级的运行时分析方案,适合生产场景。

GC 日志(定位 GC 问题)

  • JDK8 典型参数:

    -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log
    
  • JDK11+ 推荐:

    -Xlog:gc*:file=./gc.log:time,tags:filecount=5,filesize=10M
    

GC 日志能帮助你判断是否是 GC 频繁触发(GC overhead)而非真实内存泄漏。

常见解决策略(按场景给建议)

快速应急(治标)

  • 临时增大堆内存:在命令行加入 -Xmx(例如 -Xmx2g)。适合内存确实不足,但要谨慎,可能掩盖泄漏。
  • 配置堆转储-XX:+HeapDumpOnOutOfMemoryError 实战必备。

根本修复(治本)

  • 查找内存泄漏源头:用 heap dump / MAT 找到被 GC Roots 持有的对象链,定位泄漏点。
  • 释放不必要的引用:例如清空缓存、避免使用长生命周期的静态集合存放临时对象。
  • 改用弱/软引用:例如 WeakReferenceSoftReference 或使用 WeakHashMap 来缓存可回收对象(谨慎使用)。
  • 限制缓存容量:使用 LRU(如 Guava Cache)并设置最大容量和过期策略。
  • 优先使用流式/分块处理:处理大文件或大数据时,使用流/分段操作,避免一次性读入内存。
  • 优化数据结构:大量小对象可改为紧凑数组或使用原始类型数组(int[] 而非 Integer[]),或使用高性能集合(Trove、fastutil)减少装箱开销。
  • 检查第三方库:有时是第三方库(缓存/连接池)泄漏。升级或替换。

特殊类型 OOM 的处理

  • Metaspace OOM:类加载过多或动态生成类导致,解决:-XX:MaxMetaspaceSize 增大,或查找 ClassLoader 泄漏(常见于热部署/框架反复加载)。
  • Direct memory OOM:如果使用 NIO 直接缓冲区(ByteBuffer.allocateDirect),限制由 -XX:MaxDirectMemorySize 控制。
  • Native memory OOM:JVM 之外的 native 分配(例如 JNI、第三方库、线程栈),需使用系统工具(pmap / top / ps)和 native 专用分析工具。

实战技巧与最佳实践(工程化建议)

  1. 把监控放在第一位:在生产环境中用 APM 或 JMX 监控堆使用、GC 时长与 DirectMemory 使用。
  2. 把堆设置合理化:了解机器内存与 JVM 实例数量,合理设置 -Xmx-Xms,避免过度交换。
  3. 缓存策略:为缓存设置大小上限并监控命中率和内存使用。
  4. 避免不必要的全局静态变量:很多泄漏恰恰来自“方便”但危险的静态集合。
  5. 使用连接池/资源池:避免短生命资源频繁创建销毁造成内存抖动。
  6. 测试环境做压测:用更小的堆做压测,提前暴露内存问题(例如用 -Xmx128m 做压力测试)。
  7. CI 中做内存回归测试:每次依赖升级后跑内存/性能回归,避免引入第三方内存回归 bug。

常见问答(QA)

Q:我可以只通过增大 -Xmx 来解决所有 OOM 吗?
A:不推荐。增大堆只是暂时缓解,内存泄漏会继续增长,最终仍会 OOM。应结合堆分析查根因。

Q:heap.hprof 很大,如何分析?
A:用 Eclipse MAT,它能自动给出 Leak Suspects 报告,指出持有内存最多的对象和引用链。jvisualvm 也能打开并交互查看。

Q:如何定位 Metaspace 泄漏?
A:查看 jcmd <pid> VM.class_histo 或 jvisualvm 的 PermGen/Metaspace 图;如果类数量一直增长,检查 ClassLoader 泄漏,如使用了动态代理/热部署。

Q:我在容器(Docker / K8s)里,OOM 怎么办?
A:容器里请把容器内存和 JVM 堆配合好,避免 JVM 看到的主机内存比实际少导致 OOM。优先使用 cgroup-aware JDK(JDK10+ 更好),并监控容器级别内存使用与 OOMKilled 事件。

总结

OutOfMemoryError 是开发/运维常见问题,定位逻辑分为“确认类型 → 生成/抓取堆快照 → 分析占用对象 → 修复”(释放引用 / 优化内存 / 合理设置 JVM 参数)。本文提供了两套可运行 Demo(瞬时大对象 & 内存泄漏),并给出了常用命令(jmapjcmdjvisualvm、heap dump)与修复策略。遇到 OOM,别慌,按步骤分析,定位到持有对象和引用链,往往就能找到根因并修复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98036.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云服务器配置ssl-docker nginx

# 切换到您当前的目录 cd /AAAAAAAAAAAA# 创建存放nginx配置、证书和日志的目录结构 mkdir -p nginx-config/conf.d nginx-ssl nginx-logs# 为挂载做准备&#xff0c;您可能需要将当前dist目录内容移动到新的html目录 # 首先查看当前dist目录的内容 ls -la dist/# 如果html目录…

2025全球生成式引擎优化(GEO)服务商发展趋势与企业赋能白皮书

引言&#xff1a;人工智能技术的迅猛发展&#xff0c;特别是在生成式AI领域的突破&#xff0c;正以前所未有的力量重塑商业世界的竞争格局。对于寻求提升在线可见性、优化品牌互动及实现可持续增长的企业而言&#xff0c;生成式引擎优化&#xff08;GEO&#xff09;已然成为数字…

海康威视工业相机SDK开发实战:使用C/C++实现软件触发图像采集(含详细中文注释代码)

一、前言 在机器视觉、自动化检测、智能制造等领域&#xff0c;工业相机是获取图像数据的核心设备。海康威视作为国内领先的机器视觉厂商&#xff0c;其工业相机产品线丰富&#xff0c;广泛应用于各类工业场景。 本文将带你从零开始&#xff0c;使用 海康MVS SDK&#xff08;Ma…

Modbus RTU 协议介绍

Modbus RTU 协议介绍 异步串行传输方式&#xff0c;采用二进制格式&#xff0c;适用于串行通讯&#xff08;如RS-485&#xff09;&#xff0c;效率高&#xff0c;是工业现场的主流选择。 主站是Master&#xff0c;从站是Slave。 Modbus RTU 协议格式 帧结构 地址码&#xf…

TCP/IP函数——sendmsg

sendmsg() 是 POSIX 标准中一个高级套接字发送函数,属于系统调用(由操作系统内核实现),定义在 <sys/socket.h> 头文件中。它的核心特点是支持复杂消息结构,不仅能发送常规数据,还能附加控制信息(如辅助数据、IP 选项等),适用于 TCP、UDP 等多种协议,功能比 sen…

运动控制中的插值运动(插补运动):原理、实现与应用

在自动化设备中,从起点到终点的精准轨迹控制是核心需求。当目标轨迹是直线、圆弧或复杂曲线时,仅通过离散的目标点无法实现平滑运动,这就需要插值运动(Interpolation Motion)技术 —— 通过控制算法在已知路径点之间计算出连续的中间点,使运动部件沿预定轨迹平滑移动。本…

GMT——用于人形全身控制的通用运动跟踪:两阶段师生训练框架下,全身基于单一策略,且自适应采样、MoE架构

前言 如此文《KungfuBot——基于物理约束和自适应运动追踪的人形全身控制PBHC&#xff0c;用于学习打拳或跳舞(即RL下的动作模仿和运控)》的开头所说 如此&#xff0c;便关注到最新出来的三个工作 第一个是GMT: General Motion Tracking for Humanoid Whole-Body Control第二个…

matlab版本粒子群算法(PSO)在路径规划中的应用

基于粒子群优化&#xff08;PSO&#xff09;算法的路径规划 MATLAB代码实现 1. 初始化环境和参数 % 初始化环境参数 mapSize [10, 10]; % 地图大小 startPoint [1, 1]; % 起点 endPoint [9, 9]; % 终点 obstacles [3, 3; 5, 5; 7, 7]; % 障碍物位置% PSO参数 numParticles …

Go语言面试:传值与传引用的区别及选择指南

在Go语言中&#xff0c;函数参数的传递方式有两种&#xff1a;传值&#xff08;pass-by-value&#xff09;和传引用&#xff08;pass-by-reference&#xff09;。理解这两种方式的区别及其适用场景&#xff0c;是成为Go语言开发高手的必备技能。本文将深入探讨Go语言中传值与传…

数据无言,网关有声 耐达讯自动化RS485转Profinet让千年液位数据“开口说话”

在能源行业的数字化转型浪潮中&#xff0c;你是否曾面临这样的困境&#xff1a; 现场大量采用RS485接口的液位计&#xff0c;数据孤立如信息孤岛&#xff0c;无法接入Profinet高速网络&#xff1f; 模拟信号传输距离受限&#xff0c;抗干扰能力弱&#xff0c;导致液位测量误差…

出口退税新政大提速:企业如何抓住政策红利,提升最高13%纯利?

近年来&#xff0c;出口退税政策的优化与升级&#xff0c;正在成为外贸企业提升资金周转率和利润率的关键。国家税务总局发布的 2022年第9号公告&#xff08;简称“9号公告”&#xff09;落地执行已两年&#xff0c;外贸行业普遍感受到退税速度显著加快&#xff0c;平均退税周期…

使用pytorch创建/训练/推理OCR模型

一、任务描述 从手写数字图像中自动识别出对应的数字&#xff08;0-9&#xff09;” 的问题&#xff0c;属于单标签图像分类任务&#xff08;每张图像仅对应一个类别&#xff0c;即 0-9 中的一个数字&#xff09; 1、任务的核心定义&#xff1a;输入与输出 输入&#xff1a;28…

新启航开启深孔测量新纪元:激光频率梳技术攻克光学遮挡,达 130mm 深度 2μm 精度

摘要&#xff1a;本文聚焦于深孔测量领域&#xff0c;介绍了一种创新的激光频率梳技术。该技术成功攻克传统测量中的光学遮挡难题&#xff0c;在深孔测量深度达 130mm 时&#xff0c;可实现 2μm 的高精度测量&#xff0c;为深孔测量开启了新的发展篇章。关键词&#xff1a;激光…

GEO优化推荐:AI搜索新纪元下的品牌内容权威构建

引言&#xff1a;AI搜索引擎崛起与GEO策略的战略重心转移2025年&#xff0c;以ChatGPT、百度文心一言、DeepSeek为代表的AI搜索引擎已深入成为公众信息获取的核心渠道。这标志着品牌营销策略的重心&#xff0c;正从传统的搜索引擎优化&#xff08;SEO&#xff09;加速向生成式引…

uniapp的上拉加载H5和小程序

小程序配置{"path": "list/course-list","style": {"navigationBarTitleText": "课程列表","enablePullDownRefresh": true,"onReachBottomDistance": 150}}上拉拉触底钩子onReachBottom() {var that …

【和春笋一起学C++】(四十)抽象数据类型

抽象数据类型&#xff08;abstract data type, ADT&#xff09;以通用的方式描述数据类型。C中类的概念非常适合于ADT方法。例如&#xff0c;C程序通过堆栈来管理自动变量&#xff0c;堆栈可由对它执行的操作来描述。可创建空堆栈&#xff1b;可将数据项添加到堆顶&#xff08;…

大文件断点续传解决方案:基于Vue 2与Spring Boot的完整实现

大文件断点续传解决方案:基于Vue 2与Spring Boot的完整实现 在现代Web应用中,大文件上传是一个常见但具有挑战性的需求。传统的文件上传方式在面对网络不稳定、大文件传输时往往表现不佳。本文将详细介绍如何实现一个支持断点续传的大文件上传功能,结合Vue 2前端和Spring Bo…

LeNet-5:手写数字识别经典CNN

配套讲解视频&#xff0c;点击下方名片获取20 世纪 90 年代&#xff0c;计算机已经能识别文本&#xff0c;但图片识别很困难。比如银行支票的手写数字识别&#xff0c;传统方法需要人工设计规则&#xff0c;费时费力且精度不高。 于是&#xff0c;Yann LeCun 及其团队提出了 Le…

如何在 C# 中将文本转换为 Word 以及将 Word 转换为文本

在现代软件开发中&#xff0c;处理文档内容是一个非常常见的需求。无论是生成报告、存储日志&#xff0c;还是处理用户输入&#xff0c;开发者都可能需要在纯文本与 Word 文档之间进行转换。有时需要将文本转换为 Word&#xff0c;以便生成结构化的 .docx 文件&#xff0c;使内…

Open SWE:重构代码协作的智能范式——从规划到PR的全流程自动化革命

在软件开发的演进史上,工具链的每一次革新都深刻重塑着开发者的工作方式。LangChain AI推出的Open SWE,作为首个开源的异步编程代理,正在重新定义代码协作的边界——它不再仅仅是代码生成工具,而是构建了从代码库分析、方案规划、代码实现到拉取请求创建的端到端自动化工作…