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

相关文章
|
5天前
|
缓存 JavaScript Java
常见java OOM异常分析排查思路分析
Java虚拟机(JVM)遇到内存不足时会抛出OutOfMemoryError(OOM)异常。常见OOM情况包括:1) **Java堆空间不足**:大量对象未被及时回收或内存泄漏;2) **线程栈空间不足**:递归过深或大量线程创建;3) **方法区溢出**:类信息过多,如CGLib代理类生成过多;4) **本机内存不足**:JNI调用消耗大量内存;5) **GC造成的内存不足**:频繁GC但效果不佳。解决方法包括调整JVM参数(如-Xmx、-Xss)、优化代码及使用高效垃圾回收器。
58 15
常见java OOM异常分析排查思路分析
|
19天前
|
小程序 JavaScript Java
【Java】服务CPU占用率100%,教你用jstack排查定位
本文详细讲解如何使用jstack排查定位CPU高占用问题。首先介绍jstack的基本概念:它是诊断Java应用程序线程问题的工具,能生成线程堆栈快照,帮助找出程序中的瓶颈。接着,文章通过具体步骤演示如何使用`top`命令找到高CPU占用的Java进程及线程,再结合`jstack`命令获取堆栈信息并进行分析,最终定位问题代码。
99 1
【Java】服务CPU占用率100%,教你用jstack排查定位
|
20天前
|
Dubbo Java 关系型数据库
Java SPI机制分析
文章深入分析了Java SPI机制,以JDBC为例,详细探讨了服务提供者接口的发现、加载过程,并提供了一个序列化服务的实战示例,展示了如何使用ServiceLoader进行服务发现和扩展。
15 3
|
13天前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
9 0
|
18天前
|
存储 消息中间件 监控
Java日志详解:日志级别,优先级、配置文件、常见日志管理系统ELK、日志收集分析
Java日志详解:日志级别,优先级、配置文件、常见日志管理系统、日志收集分析。日志级别从小到大的关系(优先级从低到高): ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF 低级别的会输出高级别的信息,高级别的不会输出低级别的信息
|
2天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
13天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
74 6
【Java学习】多线程&JUC万字超详解
|
6天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
6天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
2天前
|
Java 调度 开发者
Java中的多线程基础及其应用
【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。