常见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

相关文章
|
1月前
|
缓存 JavaScript Java
常见java OOM异常分析排查思路分析
Java虚拟机(JVM)遇到内存不足时会抛出OutOfMemoryError(OOM)异常。常见OOM情况包括:1) **Java堆空间不足**:大量对象未被及时回收或内存泄漏;2) **线程栈空间不足**:递归过深或大量线程创建;3) **方法区溢出**:类信息过多,如CGLib代理类生成过多;4) **本机内存不足**:JNI调用消耗大量内存;5) **GC造成的内存不足**:频繁GC但效果不佳。解决方法包括调整JVM参数(如-Xmx、-Xss)、优化代码及使用高效垃圾回收器。
105 15
常见java OOM异常分析排查思路分析
|
2天前
|
安全 网络协议 Java
Java反序列化漏洞与URLDNS利用链分析
Java反序列化漏洞与URLDNS利用链分析
18 3
|
19天前
|
Java
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
本文介绍了拼多多面试中的模拟拼团问题,通过使用 `CyclicBarrier` 实现了多人拼团成功后提交订单并支付的功能。与之前的 `CountDownLatch` 方法不同,`CyclicBarrier` 能够确保所有线程到达屏障点后继续执行,并且屏障可重复使用。文章详细解析了 `CyclicBarrier` 的核心原理及使用方法,并通过代码示例展示了其工作流程。最后,文章还提供了 `CyclicBarrier` 的源码分析,帮助读者深入理解其实现机制。
|
1月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
19 0
|
13天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
33 2
|
2天前
|
Java 关系型数据库 MySQL
如何用java的虚拟线程连接数据库
本文介绍了如何使用Java虚拟线程连接数据库,包括设置JDK版本、创建虚拟线程的方法和使用虚拟线程连接MySQL数据库的示例代码。
16 6
如何用java的虚拟线程连接数据库
|
6天前
|
Java 数据库 UED
Java的多线程有什么用
Java的多线程技术广泛应用于提升程序性能和用户体验,具体包括:提高性能,通过并行执行充分利用多核CPU;保持响应性,使用户界面在执行耗时操作时仍流畅交互;资源共享,多个线程共享同一内存空间以协同工作;并发处理,高效管理多个客户端请求;定时任务,利用`ScheduledExecutorService`实现周期性操作;任务分解,将大任务拆分以加速计算。多线程尤其适用于高并发和并行处理场景。
|
17天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
1天前
|
Java 调度
Java一个线程的生命周期详解
Java中,一个线程的生命周期分为五个阶段:NEW(新建),RUNNABLE(可运行),BLOCKED(阻塞),WAITING(等待),TERMINATED(终止)。线程创建后处于新建状态,调用start方法进入可运行状态,执行中可能因等待资源进入阻塞或等待状态,正常完成或异常终止后进入终止状态。各状态间可相互转换,构成线程的生命周期。
|
1天前
|
Java API 调度
Java 多线程编程详解
《Java多线程编程详解》深入浅出地讲解了Java平台下的多线程核心概念、API使用及最佳实践。从基础理论到实战案例,本书帮助读者掌握并发编程技巧,提升软件开发中的效率与性能,是Java开发者不可或缺的参考指南。