常见java OOM异常分析排查思路分析

简介: Java虚拟机(JVM)遇到 OutOfMemoryError(OOM)表示内存资源不足。常见OOM情况包括:1) **Java堆空间不足**:内存被大量对象占用且未及时回收,或内存泄漏;解决方法包括调整JVM堆内存大小、优化代码及修复内存泄漏。2) **线程栈空间不足**:单线程栈帧过大或频繁创建线程;可通过优化代码或调整-Xss参数解决。3) **方法区溢出**:运行时生成大量类导致方法区满载;需调整元空间大小或优化类加载机制。4) **本机内存不足**:JNI调用或内存泄漏引起;需检查并优化本机代码。5) **GC造成的内存不足**:频繁GC但效果不佳;需优化JVM参数、代码及垃圾回收器

Java 虚拟机(JVM)发生 OutOfMemoryError(OOM)异常时,表示 JVM 在尝试分配内存时无法找到足够的内存资源。以下是几种常见的导致 OOM 异常的情况:

1. Java 堆空间不足 (Java Heap Space)

这种情况发生在 JVM 堆内存耗尽,无法再为新的对象分配空间。

原因

  • 创建了大量对象且无法及时被垃圾回收。
  • 内存泄漏:对象持有引用无法被垃圾回收。
  • 内存中缓存过多数据。

解决方案

  • 调整 JVM 堆内存大小(增加 -Xmx 参数)。
  • 优化代码,减少内存消耗。
  • 检查并修复内存泄漏。

Java 堆溢出排查解决思路

1.查找关键报错信息,比如 java.lang.OutOfMemoryError: Java heap space

2.使用内存映像分析工具(如Jprofiler)对Dump出来的堆储存快照进行分析,分析清楚是内存泄漏还是内存溢出。

这里给出我安装整合idea参考的教程

JProfiler 11 安装与破解 - 哑吧 - 博客园

Intellij IDEA集成JProfiler性能分析神器-CSDN博客

3.如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,修复应用程序中的内存泄漏。

4.如果不存在泄漏,先检查代码是否有死循环,递归等,再考虑用 -Xmx 增加堆大小。 demo代码:

java

代码解读

复制代码

import java.util.ArrayList;
import java.util.List;

public class HeapOOM {
 static class OOMObject {
 }
 public static void main(String[] args) {
 List<OOMObject> list = new ArrayList<OOMObject>();
 //在堆中无限创建对象
 while (true) {
            list.add(new OOMObject());
 }
 }
}

按照排除解决方案。

1.查找报错关键信息

arduino

代码解读

复制代码

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

2.使用内存映像分析工具Jprofiler分析产生的堆储存快照

(1)我们可以先通过 top -c查看当前服务器进程并记录当前消耗cpu最高线程的pid。

比如发现当前线程pid为744的使用率最高。

(2)然后通过下面的命令到处jvm内存快照

ini

代码解读

复制代码

jmap -dump:formart=b.file=java_pid_744.hprof 744
(java_pid_744.hprof是文件名。 744是通过top c查看消耗cpu使用率最高的线程id)  
然后下载到本地,下载先可以先压缩一下,这样可以节省时间。一个小技巧。

(3)使用上面下载好的JProfiler打开生成的单个快照

OOMObject这个类创建了11956010个实例,是属于内存溢出

然后点击这个最大对象分析

然后我这时候电脑卡着了,借用网图给接下来步骤说明

打开后右键打开使用选定对象

然后这里会显示详细的日志

这里可以看见具体的代码块。然后我们就可以定位代码结合具体代码进行分析。发现死循环了。

2.线程栈空间不足 (Stack Overflow)

关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
  • 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

原因

  • 在单个线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出StackOverflowError 异常。
  • 不断地建立线程的方式会导致内存溢出。

解决方案

  • 优化代码,避免过深的递归调用。
  • 调整线程栈大小(增加 -Xss 参数)。

栈溢出排查解决思路

查找关键报错信息,确定是StackOverflowError还是OutOfMemoryError 如果是StackOverflowError,检查代码是否递归调用方法等 如果是OutOfMemoryError,检查是否有死循环创建线程等,通过-Xss降低的每个线程栈大小的容量

demo代码

typescript

代码解读

复制代码

public class JavaVMStackOOM {
    private void dontStop() {
        while (true) {

        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }

}

1.报错信息 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

2.定位dontStop 方法是一个无限循环,线程一旦执行这个方法,将会一直循环下去

3.排查代码,确定是否显示使用死循环创建线程

3.方法区溢出

方法区,(又叫永久代,JDK8后,元空间替换了永久代),用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。

方法区溢出原因

使用CGLib生成了大量的代理类,导致方法区被撑爆 在Java7之前,频繁的错误使用String.intern方法 大量jsp和动态产生jsp 应用长时间运行,没有重启

方法区溢出排查解决思路

调整元空间大小(增加 -XX:MaxMetaspaceSize 参数) 检查代码是否频繁错误得使用String.intern方法 优化类加载机制,减少不必要的类加载,检查是否使用CGLib生成了大量的代理类 重重启JVM

4.本机内存不足 (Native Memory Exhaustion)

这种情况发生在本机内存耗尽时。

原因

  • 本机代码分配了大量内存(如 JNI 调用)。
  • 内存泄漏。

解决方案

  • 检查并优化本机代码。
  • 确保本机内存使用合理。

比如: NIO程序中,使用ByteBuffer.allocteDirect(capability)分配的是直接内存,可能导致直接内存溢出。

ByteBuffer分配128MB直接内存,而JVM参数-XX:MaxDirectMemorySize=100M指定最大是100M,因此发生直接内存溢出。

5.GC 造成的内存不足 (GC Overhead Limit Exceeded)

这种情况发生在垃圾回收频繁且回收效果不明显时(超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。)。

原因

  • 程序创建对象过快,垃圾回收无法跟上。
  • 内存不足,垃圾回收无法有效清理。

解决方案

  • 检查JVM参数-Xmx -Xms是否合理
  • 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
  • 增加 JVM 堆内存大小。
  • 优化代码,减少对象创建速度。
  • 使用更高效的垃圾回收器(如 G1 GC)。

demo

typescript

代码解读

复制代码

public class GCoverheadTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    //do nothing
                }
            });
        }
    }

}
  • 任务积压:线程池的大小是 10,这意味着同时最多只有 10 个任务在执行。其余的任务会被放入线程池的任务队列中等待执行。由于循环是无限的,任务会不断地被提交,导致任务队列不断增大。
  • 内存消耗:随着任务队列中的任务越来越多,系统的内存消耗也会不断增加。最终,可能会导致内存耗尽,抛出 OutOfMemoryError 异常。
  • 线程池饱和:线程池中的 10 个线程会不断地从任务队列中取任务执行,但由于每个任务都要休眠 10 秒钟,任务处理的速度远远跟不上任务提交的速度,导致任务队列越来越长。


转载来源:https://juejin.cn/post/7388278660148936754

相关文章
|
2月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
191 4
|
10天前
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
52 2
|
1月前
|
人工智能 Java
Java参数传递分析
本文详细探讨了Java中参数传递的机制,明确指出Java采用的是值传递而非引用传递。通过基本数据类型(如int)和引用类型(如Map、自定义对象People)的实例测试,证明方法内部对参数的修改不会影响原始变量。即使在涉及赋值返回的操作中,表面上看似引用传递,实际仍是值传递的结果。文中结合代码示例与执行结果,深入解析了值传递的本质及容易引起混淆的情形,帮助读者准确理解Java参数传递的核心概念。
|
27天前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
34 0
|
27天前
|
Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析
wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。
40 0
|
2月前
|
监控 数据可视化 Java
调试技巧 - 用Linux命令排查Java问题
总的来说,使用Linux命令来排查Java问题,需要一定的实践经验和理论知识。然而,只要我们愿意花时间深入了解这些工具,我们就能够熟练地使用它们来分析和解决问题。此外,这些工具只是帮助我们定位问题,真正解决问题需要我们对Java和JVM有深入的理解,并能够读懂和分析代码。
90 13
|
2月前
|
存储 安全 Java
Java 集合框架详解:系统化分析与高级应用
本文深入解析Java集合框架,涵盖List、Set、Map等核心接口及其常见实现类,如ArrayList、HashSet、HashMap等。通过对比不同集合类型的特性与应用场景,帮助开发者选择最优方案。同时介绍Iterator迭代机制、Collections工具类及Stream API等高级功能,提升代码效率与可维护性。适合初学者与进阶开发者系统学习与实践。
77 0
|
3月前
|
人工智能 JSON Java
列表结构与树结构转换分析与工具类封装(java版)
本文介绍了将线性列表转换为树形结构的实现方法及工具类封装。核心思路是先获取所有根节点,将其余节点作为子节点,通过递归构建每个根节点的子节点。关键在于节点需包含 `id`、`parentId` 和 `children` 三个属性。文中提供了两种封装方式:一是基于基类 `BaseTree` 的通用工具类,二是使用函数式接口实现更灵活的方式。推荐使用后者,因其避免了继承限制,更具扩展性。代码示例中使用了 Jackson 库进行 JSON 格式化输出,便于结果展示。最后总结指出,理解原理是进一步优化和封装的基础。
|
18天前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
|
4月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
261 60
【Java并发】【线程池】带你从0-1入门线程池