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


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

相关推荐
gmaajt2 小时前
Golang怎么做国际化多语言_Golang i18n教程【核心】
jvm·数据库·python
maqr_1102 小时前
CSS如何利用Sass定义全局阴影方案_通过变量实现统一CSS风格
jvm·数据库·python
m0_613856292 小时前
uni-app怎么做类似于美团的商家评价星级 uni-app五星评分组件制作【实战】
jvm·数据库·python
2401_833033623 小时前
如何修复固定定位头部容器中悬浮下拉菜单的错位问题
jvm·数据库·python
z4424753264 小时前
CSS Grid布局如何实现网格项目的自动增长_设置grid-auto-flow- row
jvm·数据库·python
m0_740352424 小时前
如何在 SvelteKit 中为动态加载的图片实现响应式悬停覆盖层
jvm·数据库·python
gmaajt4 小时前
JavaScript中闭包对垃圾回收器GC标记清除算法的影响
jvm·数据库·python
m0_495496414 小时前
C#怎么操作音频文件 C#如何用NAudio播放录制和处理WAV MP3音频文件【工具】
jvm·数据库·python
dFObBIMmai5 小时前
CSS如何检测页面浮动元素位置_使用审查工具与clear
jvm·数据库·python
qq_460978405 小时前
实现 Svelte 中基于数组索引的 details 元素单开单关交互
jvm·数据库·python