每日一题--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 调优经验!


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

相关推荐
她说..1 天前
Java 基本数据类型高频面试题
java·开发语言·jvm·spring boot
明灯伴古佛1 天前
面试:什么是可重入性?为什么 synchronized 是可重入锁?
java·jvm·面试
PrDf22Iw81 天前
编译执行与解释执行的区别是什么?JVM 使用哪种方式?
jvm
!停1 天前
C++入门—内存管理
java·jvm·c++
Engineer邓祥浩1 天前
JVM学习笔记(5) 第二部分 自动内存管理 第4章 虚拟机性能监控、故障处理工具
jvm·笔记·学习
woai33641 天前
JVM学习-基础篇-垃圾回收
java·jvm·学习
aP8PfmxS21 天前
Lab3-page tables && MIT6.1810操作系统工程【持续更新】
java·linux·jvm
Derrick__12 天前
Android混淆和加密技术
android·jvm·python
LSL666_2 天前
JVM——OOM异常
jvm
minji...2 天前
Linux 多线程(一)线程概念,轻量级进程,执行流,线程创建
java·开发语言·jvm