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

JVM中产生OOM(内存溢出)的8种典型情况及解决方案

Java中的OutOfMemoryError(OOM)是当JVM内存不足时抛出的错误。本文将全面剖析JVM中产生OOM的各种情况,包括堆内存溢出、方法区溢出、栈溢出等,并提供详细的诊断方法和解决方案。

一、OOM基础概念


1.1 OOM错误类型

  • Java中的OOM是java.lang.OutOfMemoryError的子类,常见的有:
  • Java heap space:堆空间不足
  • GC Overhead limit exceeded:GC效率低下
  • PermGen space/Metaspace:方法区溢出
  • Unable to create new native thread:线程创建失败
  • Requested array size exceeds VM limit:数组过大
  • Direct buffer memory:直接内存溢出
  • Code cache:代码缓存区满
  • Kill process or sacrifice child:Linux系统级限制

二、堆内存溢出(Java heap space)


2.1 产生原因


当对象需要分配到堆内存时,如果堆内存不足且无法通过GC回收足够空间时抛出。

// 典型示例

public class HeapOOM {public static void main(String[] args) {List<Object> list = new ArrayList<>();while(true) {list.add(new byte[1024*1024]); // 每次分配1MB}}
}


2.2 错误信息

java.lang.OutOfMemoryError: Java heap space


2.3 解决方案


调整堆大小:

-Xms256m -Xmx1024m  # 初始堆256MB,最大堆1GB



内存分析:

使用jmap获取堆转储:

jmap -dump:format=b,file=heap.hprof <pid>


使用MAT/Eclipse Memory Analyzer分析


代码优化:

避免内存泄漏(如静态集合、未关闭资源)
使用对象池重用对象


三、GC开销超限(GC Overhead limit exceeded)


3.1 产生原因


当JVM花费超过98%的时间进行GC,但只恢复了不到2%的堆空间时抛出。

// 典型场景:创建大量生命周期短的对象

public class GCOverheadOOM {public static void main(String[] args) {Map<Key, String> map = new HashMap<>();while(true) {for(int i=0; i<10000; i++) {map.put(new Key(i), "Value"+i);}map.clear(); // 不完全清除}}
}


3.2 错误信息

java.lang.OutOfMemoryError: GC Overhead limit exceeded


3.3 解决方案

  1. 增加堆大小:
    ​
    -Xmx2g -XX:+UseG1GC​

  2. 优化GC策略:
  • 对于大量短生命周期对象,使用G1或ZGC
  • 调整新生代大小:
  • -XX:NewRatio=2  # 新生代占堆的1/3

  1. 代码改进:
  • 减少临时对象创建
  • 使用更高效的数据结构

四、方法区溢出(Metaspace/PermGen)


4.1 产生原因


JDK8前称为PermGen space,JDK8+称为Metaspace,存储类元数据信息。

// 通过动态生成类填满方法区
public class MetaspaceOOM {static class OOMObject {}public static void main(String[] args) {while(true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, objects));enhancer.create(); // 动态创建类}}
}


4.2 错误信息
 

// JDK7及之前
java.lang.OutOfMemoryError: PermGen space// JDK8+
java.lang.OutOfMemoryError: Metaspace


4.3 解决方案


调整Metaspace大小:

-XX:MaxMetaspaceSize=256m


JDK8前调整PermGen:

-XX:MaxPermSize=128m


减少动态类生成:

缓存动态代理类
限制反射使用


五、线程栈溢出(Unable to create new native thread)


5.1 产生原因


当创建线程数量超过系统限制时发生。

public class ThreadOOM {public static void main(String[] args) {while(true) {new Thread(() -> {try { Thread.sleep(100000); } catch(InterruptedException e) {}}).start();}}
}


5.2 错误信息

java.lang.OutOfMemoryError: Unable to create new native thread


5.3 解决方案


减少线程数量:

使用线程池:

ExecutorService pool = Executors.newFixedThreadPool(100);

调整系统限制:

ulimit -u  # 查看最大线程数
ulimit -u 2048  # 设置最大线程数



减少栈大小:

-Xss256k  # 默认1MB,减少可创建更多线程



六、直接内存溢出(Direct buffer memory)


6.1 产生原因


NIO使用的直接内存(堆外内存)不足时抛出。

public class DirectMemoryOOM {public static void main(String[] args) {// 绕过DirectByteBuffer限制,直接分配内存List<ByteBuffer> buffers = new ArrayList<>();while(true) {buffers.add(ByteBuffer.allocateDirect(1024*1024)); // 1MB}}
}


6.2 错误信息

java.lang.OutOfMemoryError: Direct buffer memory



6.3 解决方案


调整直接内存大小:

-XX:MaxDirectMemorySize=256m


显式回收:

((DirectBuffer)buffer).cleaner().clean();


使用池化技术:

Netty的ByteBuf池


七、数组过大溢出(Requested array size exceeds VM limit)


7.1 产生原因


尝试分配超过JVM限制的数组。

public class ArraySizeOOM {public static void main(String[] args) {int[] arr = new int[Integer.MAX_VALUE]; // 约2^31-1个元素}
}



7.2 错误信息

java.lang.OutOfMemoryError: Requested array size exceeds VM limit


7.3 解决方案
减小数组大小:

分块处理大数据
使用集合替代:

List<Integer> list = new ArrayList<>();


调整数据结构:

使用数据库或文件存储


八、代码缓存溢出(Code cache)


8.1 产生原因


JIT编译的代码填满代码缓存区。

// 通常由大量方法被JIT编译导致

public class CodeCacheOOM {public static void main(String[] args) {// 需要大量方法编译的代码}
}



8.2 错误信息

java.lang.OutOfMemoryError: Code cache



8.3 解决方案
增加代码缓存大小:

-XX:ReservedCodeCacheSize=256m



减少编译阈值:

-XX:CompileThreshold=10000



关闭分层编译:

-XX:-TieredCompilation



九、系统级OOM(Kill process or sacrifice child)


9.1 产生原因
Linux系统的OOM Killer终止进程。

dmesg | grep -i kill


输出示例:

Out of memory: Kill process 12345 (java) score 999 or sacrifice child



9.2 解决方案
增加系统内存
调整OOM Killer策略:

echo -17 > /proc/[pid]/oom_adj


限制容器内存(Docker):

docker run -m 2g my-java-app



十、OOM诊断工具链

OOM诊断工具链
工具用途示例命令
jstat监控内存和GCjstat -gcutil <pid> 1000
jmap堆转储jmap -dump:live,format=b,file=heap.hprof <pid>
jvisualvm可视化分析图形化界面
MAT内存分析分析hprof文件
jcmd多功能工具jcmd <pid> VM.native_memory


十一、OOM预防最佳实践


代码层面:

避免内存泄漏(监听器、静态集合)
及时关闭资源(数据库连接、文件流)
使用WeakReference处理缓存
JVM配置:

# 基础配置示例

-Xms1g -Xmx2g -XX:MaxMetaspaceSize=256m 
-XX:+UseG1GC -XX:MaxGCPauseMillis=200


监控预警:

JMX监控堆内存使用
Prometheus + Grafana监控体系
设置合理的GC日志监控:

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



十二、总结


OOM类型与对应解决方案速查表:

OOM类型相关内存区域典型解决方案
Java heap space增大堆,修复内存泄漏
GC Overhead优化GC策略,减少对象创建
Metaspace/PermGen方法区增大Metaspace,减少动态类生成
Unable to create thread减少线程数,调整-Xss
Direct buffer直接内存增大MaxDirectMemorySize,显式回收
Array size减小数组尺寸,分块处理
Code cacheJIT代码缓存增大ReservedCodeCacheSize
System OOM系统内存增加物理内存,调整OOM Killer

http://www.xdnf.cn/news/19824.html

相关文章:

  • 初识NOSQL
  • 方法决定效率
  • git: 取消文件跟踪
  • SRE团队是干嘛的
  • 关于IDE的相关知识之一【使用技巧】
  • Spring Security 如何使用@PreAuthorize注解
  • Nano Banana 新玩法超惊艳!附教程案例提示词!
  • AI 设计工具天花板
  • 【android bluetooth 协议分析 21】【ble 介绍 3】【ble acl Supervision Timeout 介绍】
  • 黑马头条面试重点业务
  • 构建下一代智能金融基础设施
  • SpringBoot--手写日期格式转换工具类
  • TiDB v8.5.3 单机集群部署指南
  • ASP.NET Core上传文件到minio
  • 【leetcode】236. 二叉树的最近公共祖先
  • 利用Base64传输二进制文件并执行的方法(适合没有ssh ftp等传输工具的嵌入式离线场景)
  • 研发文档版本混乱的根本原因是什么,怎么办
  • ELK 统一日志分析系统部署与实践指南(上)
  • 撤销修改 情况⼀:对于⼯作区的代码,还没有 add
  • 餐饮、跑腿、零售多场景下的同城外卖系统源码扩展方案
  • 图片移到根目录
  • Spring Boot + Spring MVC 项目结构
  • ARM汇编记忆
  • C# 简述委托,Func与Action委托。 他们之前有什么区别?
  • 告别手动复制粘贴:C# 实现 Excel 与 TXT 文本文件高效互转
  • 搭建分布式Hadoop集群[2025] 实战笔记
  • SQL分类详解:掌握DQL、DML、DDL等数据库语言类型
  • p049基于Flask的医疗预约与诊断系统
  • 删除⽂件之git
  • 避免侵权!这6个可免费下载字体网站能放心商用