每日一题--JVM内存溢出分析

JVM内存溢出(OOM)场景分析与复现指南

在 Java 开发中,内存溢出(OutOfMemoryError)是常见的 JVM 故障之一。理解其产生原因,并能够快速复现,有助于我们在实际开发中排查和预防此类问题。本文通过四个典型场景,带你深入理解 JVM 内存结构及其溢出机制。

一、JVM 内存结构回顾

在分析 OOM 之前,我们先简单回顾一下 JVM 的内存区域划分:

  • 堆(Heap):存放对象实例,也是 GC 的主要区域。
  • 方法区(Method Area):存放类信息、常量、静态变量等。在 JDK1.8 后改为元空间(MetaSpace)。
  • Java 虚拟机栈(JVM Stack):每个方法调用对应一个栈帧,存放局部变量、操作数栈、方法出口等。
  • 本地方法栈(Native Method Stack):为 JVM 调用本地方法服务。
  • 程序计数器(PC Register):记录当前线程执行的字节码行号。
  • 直接内存(Direct Memory):由 NIO 等使用,不受 JVM 堆大小限制。

二、四类 OOM 场景分析

场景1:字符串常量池溢出(stringPoolOOM

java 复制代码
public void stringPoolOOM() {
    List<String> list = new ArrayList<>();
    int i = 0;
    while (true) {
        list.add(String.valueOf(i++).intern());
    }
}
原因分析:
  • intern() 方法会将字符串放入常量池。
  • JDK1.7 后常量池位于堆中,若不断添加且不被 GC 回收,最终堆内存耗尽。
复现参数设置:
bash 复制代码
-Xms10M -Xmx10M

限制堆大小为 10MB,程序会快速抛出 java.lang.OutOfMemoryError: Java heap space


场景2:堆内存溢出(heapOOM

java 复制代码
public void heapOOM() {
    List<byte[]> list = new ArrayList<>();
    while (true) {
        list.add(new byte[1024 * 1024]);
    }
}
原因分析:
  • 不断创建大对象并持有引用,GC 无法回收。
  • 堆内存被占满后抛出 OOM。
复现参数设置:
bash 复制代码
-Xms10M -Xmx10M

与场景1相同,堆内存被迅速占满。


场景3:栈溢出(stackSOF

java 复制代码
public void stackSOF() {
    stackSOF(); // 无限递归
}
原因分析:
  • 每次方法调用都会在栈中分配一个栈帧。
  • 递归过深,栈帧超出栈容量,抛出 StackOverflowError
复现参数设置:
bash 复制代码
-Xss128k

设置每个线程的栈大小为 128KB,递归调用会更快溢出。


场景4:直接内存溢出(directMemoryOOM

java 复制代码
public void directMemoryOOM() throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    while (true) {
        unsafe.allocateMemory(1024 * 1024);
    }
}
原因分析:
  • Unsafe.allocateMemory() 分配的是直接内存,不受堆大小限制。
  • 若直接内存耗尽,抛出 OutOfMemoryError
复现参数设置:
bash 复制代码
-XX:MaxDirectMemorySize=10M

限制直接内存最大为 10MB,程序会快速抛出 OOM。


三、总结与思考

内存区域 溢出类型 关键参数 触发方式
OOM -Xmx 创建大量对象并持有引用
方法区/常量池 OOM -Xmx 不断 intern 字符串
虚拟机栈 SOF -Xss 无限递归
直接内存 OOM -XX:MaxDirectMemorySize 使用 Unsafe 分配内存

补充思考

  • 堆内存:除了对象实例,还存放字符串常量池(JDK1.7+)。
  • 栈内存:每个栈帧包含局部变量表、操作数栈、动态链接、方法出口等。
  • 方法区:JDK1.8 后元空间存储类信息、常量、静态变量等,位于本地内存。
  • 直接内存:不属于 JVM 运行时数据区,但受物理内存限制。

四、写在最后

理解 JVM 内存结构是排查 OOM 的基础。通过合理设置 JVM 参数,我们可以快速复现各种内存溢出场景,从而更好地理解其触发机制。在实际开发中,建议结合监控工具(如 JVisualVM、Arthas)对内存使用进行实时分析,提前发现潜在风险。

希望这篇文章对你有所帮助,也欢迎在评论区交流你的 JVM 调优经验!


如果有新的想法,欢迎随时和我讨论。

相关推荐
2401_8442213210 小时前
Python数据库操作:SQLAlchemy ORM指南
jvm·数据库·python
qq_4042658311 小时前
用Python批量处理Excel和CSV文件
jvm·数据库·python
qq_4181017712 小时前
使用Scikit-learn进行机器学习模型评估
jvm·数据库·python
2401_8914821713 小时前
将Python Web应用部署到服务器(Docker + Nginx)
jvm·数据库·python
为美好的生活献上中指13 小时前
*Java 沉淀重走长征路*之——《MyBatis与MyBatis-Plus一文打尽!》
java·jvm·maven·mybatis·mybatis-plus
2401_8830354615 小时前
数据分析与科学计算
jvm·数据库·python
业精于勤_荒于稀15 小时前
服务器配置
java·服务器·jvm
闻哥16 小时前
MySQL索引核心原理:B+树生成、页分裂与页合并全解析
java·jvm·b树·mysql·adb·面试·springboot
moonlight030416 小时前
对象组成、分配、强弱引用
jvm
一叶飘零_sweeeet16 小时前
从 GC 频繁到毫秒级停顿:JVM 内存调优分代配比、晋升机制与架构策略全拆解
jvm