news 2026/4/29 14:37:27

JVM的 OutOfMemoryError异常

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM的 OutOfMemoryError异常

Java堆溢出

A. 关于 “这里面讲保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象” 理解:

  1. GC Roots是不是有4类 :
    a它可以是方法局部变量表中引用,b方法区:类静态成员引用,常量引用如 static final String。 c. 本地方法栈中 JNI 引用,d. 同步锁持有的对象。 jvm内部引用(如基本类型 Class 对象如 int.class、常驻异常如 NullPointerException 类对象等)
  2. 只要上面4种持有引用,就表示GC Roots到对象之间有可达路径?

B. 当设置-XX:+HeapDumpOnOutOfMemoryError时,出现内存溢出,jvm会dump内存快照到启动 java -jar … 命令时所在目录是吗?

答: 是的。 格式长这样: java_pid.hprof, 通常只对 Java Heap Space 和 Metaspace 溢出触发,直接内存溢出(Direct buffer memory)等不会触发(需额外配置)
如下表示堆出现异常:Java heap space
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3404.hprof …
Heap dump file created [22045981 bytes in 0.663 secs]

C, 内存溢出分两种,一种是内存泄漏(Memory Leak):GC Roots的引用链一直存在,即对象一直无法被jvm回收。 第二种就内存耗尽(Memory Exhaustion) 即程序中对象必须存在,内存不足了。

关于内存泄漏处理策略是:通过Eclipse Memory Analyzer这个工具中的 Dominator Tree 表示谁控制了最多内存” 的树状结构
Shallow Heap:该对象自身占用的内存(如对象头 + 字段值,不含引用对象)
Retained Heap(单位byte):该对象及其所有可达对象(且不被其他路径引用)的总内存 → 这才是真正“它独占”的内存
Percentage:占整个堆的百分比(按 Retained Heap 计算)

  • 重点看 Retained Heap 和 Percentage —— 它告诉你:哪个对象“吃掉”了最多内存,是泄漏嫌疑最大的目标!

  • 下一步:如何深挖这个 main Thread?
    – 步骤 1:右键 java.lang.Thread @… main Thread → “Show in Dominator Tree”
    (点击左侧小三角 , 就可以看出是哪个点击Percentage最大的一个,左侧标签栏有一个Attributes,可以看具体是什么对象)
    – 步骤 2:右键该线程 → “Merge Shortest Paths to GC Roots”
    这是关键操作!它会显示:
    从 GC Root 到该线程的最短引用路径(即:谁让这个线程“活”着?它又持有哪些对象?)
    – 步骤 3:右键该线程 → “List Objects” → “with outgoing references”
    查看它直接引用了哪些对象(比如是不是有个 HashMap 或 ArrayList 占了大部分 Retained Heap)

/**

  • VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

  • @author zzm
    */
    public class HeapOOM {

    static class OOMObject {
    }

    public static void main(String[] args) {
    List list = new ArrayList();

    while (true) { list.add(new OOMObject()); }

    }
    }

虚拟机栈和本地方法栈溢出的理解:

    1. 栈溢出,jvm不区分是虚拟机栈和还是本地方法栈。
    1. jvm栈的大小通过-Xss参数 来设置,出现如下两种情况会:就会抛出StackOverflowError异常
      a. 如果超过栈的深度。如在不断递归达到指定深度时(默认Linux 64位通常为 1MB, 可以达到1000~2000层递归)
      /**
  • VM Args:-Xss128k
    */
    public class JavaVMStackSOF_1 {
    private int stackLength = 1;
    public void stackLeak() {
    stackLength++;
    stackLeak();
    }
    public static void main(String[] args) {
    JavaVMStackSOF_1 oom = new JavaVMStackSOF_1();
    try {
    oom.stackLeak();
    } catch (Throwable e) {
    System.out.println(“stack length:” + oom.stackLength);
    throw e;
    }
    }
    }
    b. 在方法中定义异常大量“基本类型”(不是引用对象的变量,对象的大小在堆中),从而增大了本地变量表的长度。

-3 -Xss参数 默认在liunx下面不能小于228k. 不然,jvm启动会有一个提示:The Java thread stack size specified is too small. Specify at least 228k

-4. 线程创建过多会出现OutOfMemoryError,如果线程栈的很大,这种情况更容易发生,这与栈空间是否足够没有关系。(这种情况在我公司的项目就遇到过)
– 这种oom的异常现象:从JDK 7起, 提示信息中“unable to create native thread”后面, 虚拟机会特别注明原因可能是“possiblyout of memory or process/resource limits reached”
– 它的原因是如果单个进程是 2G 减去 (堆+方法区(-XX:MaxMetaspaceSize)+直接内存(-XX:MaxDirectMemorySize))=》 剩下给虚拟机栈和本地方法栈分配。如果线程量大,而且每个线程-Xss又很大,就会出现“unable to create native thread”
公式:最大线程数 ≈ (物理内存 - 堆(-Xmx) - Metaspace(-XX:MaxMetaspaceSize) - 直接内存(-XX:MaxDirectMemorySize)) / (-Xss)
– 解决方法:如果无法减少线程数时,无法加大进程的内存时,可以减小堆的大小及线程栈-Xss的大小。

方法区和运行时常量池溢出的理解:

    1. 在jdk8中方法区都在元空间中,存放的内容如类名、 访问修饰符、 常量池、 字段描述、 方法描述等。 所以jdk8之后,就没有java.lang.OutOfMemoryError: PermGen space
      而是java.lang.OutOfMemoryError: Metaspace
  • 1.1 触发场景:A. 动态生成大量类(如 Spring CGLib 代理、Groovy 脚本、OSGi 模块) B. 类加载器泄漏(ClassLoader 未卸载 → 其加载的类无法回收)

    1. 字符串常量池已经放到了堆中了,以前jdk8以前是放在方法区。
      – String::intern()的行行:
      A. 会直接在堆中字符串常量池找是否存在,如果存在直接返回引用。
      B.如果不存在直接从堆中找到该string对象放到常量池中(intern()不会创建对象),并返回引用。

public static void main(String[] args) {
String str1 = new StringBuilder(“计算机”).append(“软件”).toString();
System.out.println(str1.intern() == str1); // 这个在jdk8时,是true, 因为intern()在常量池中找"计算机软件",发生是第一次,所以从堆中找该对象StringBuilder(“计算机软件”)放到常量池中。所以它们是一样的。
String str2 = new StringBuilder(“ja”).append(“va”).toString();
System.out.println(str2.intern() == str2); // 这个在jdk8时,是false,因为因为“java”这个字符串在(如 rt.jar 中的类名)就已经出现过一次, 字符串常量池中已经有了和堆中新生的new StringBuilder(“java”)不一样。
}

-3. XX: MetaspaceSize: 指定元空间的初始空间大小(默认约 20.8MB,平台相关),
当元空间使用量 > MetaspaceSize 时,下次 Full GC 会尝试卸载无用类型卸载。 -XX: MinMetaspaceFreeRatio: 作用是在垃圾收集之后控制最小的元空间剩余容量的百分比, 可减少因为元空间不足导致的垃圾收集的频率。

  1. 疑问:在项目中,需要创建超极大量的对象,从jstat -gcutil 时,Metaspace一直在90%以上。
    答: 这和创建超大量的对象与Metaspace 高占用无关。 和工程用到spring CGLIB、 AOP、ORM 等功能有关,会在运行时动态生成大量新的类

jstat -class

  • 输出示例:
  • Loaded Bytes Unloaded Bytes Time
  • 25432 52345.6 0 0.0 12.34

如果 ‘Loaded’ 数量巨大(几万甚至更多),并且 ‘Unloaded’ 为0或很少,说明类在持续加载但没有被卸载。

本机直接内存溢出的理解:

    1. 直接内存可以通过-XX:MaxDirectMemorySize参数来指定, 如果不去指定, 则默认与Java堆最大值(由-Xmx指定) 一致,但最高不超过4G.
    1. 异常信息:java.lang.OutOfMemoryError: Direct buffer memory。通过jstat -gcutil,可以发现堆的使用率很低。

AI的langchain,langgraph , java,spring,hadoop, flink流式计算, 心理学,哲学相关知识探讨.
本人邮箱:luyllyl@163.com

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 14:32:16

如何快速配置EVE Online舰船:Pyfa完整实战指南

如何快速配置EVE Online舰船:Pyfa完整实战指南 【免费下载链接】Pyfa Python fitting assistant, cross-platform fitting tool for EVE Online 项目地址: https://gitcode.com/gh_mirrors/py/Pyfa 在浩瀚的EVE Online宇宙中,每一次精准的舰船配置…

作者头像 李华
网站建设 2026/4/29 14:31:01

如何彻底解决Cursor试用限制问题:终极重置指南

如何彻底解决Cursor试用限制问题:终极重置指南 【免费下载链接】go-cursor-help 解决Cursor在免费订阅期间出现以下提示的问题: Your request has been blocked as our system has detected suspicious activity / Youve reached your trial request limit. / Too m…

作者头像 李华
网站建设 2026/4/29 14:29:53

告别玄学调优:基于RSRP/RSRQ/SINR的5G网络现场勘测与优化实战指南

告别玄学调优:基于RSRP/RSRQ/SINR的5G网络现场勘测与优化实战指南 站在5G基站的铁塔下,手里握着路测终端显示的-105dBm RSRP和12dB SINR数据,你是否曾困惑:为什么用户依然抱怨视频卡顿?传统"经验主义"的网优…

作者头像 李华
网站建设 2026/4/29 14:28:07

深度解析OpenArk:Windows系统安全分析的实战利器

深度解析OpenArk:Windows系统安全分析的实战利器 【免费下载链接】OpenArk The Next Generation of Anti-Rookit(ARK) tool for Windows. 项目地址: https://gitcode.com/GitHub_Trending/op/OpenArk 在Windows系统管理和安全分析领域,你是否曾面…

作者头像 李华