前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >常见java OOM异常分析排查思路分析

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

作者头像
阿珍
发布2024-09-06 15:23:03
810
发布2024-09-06 15:23:03

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代码:

代码语言:javascript
复制
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.查找报错关键信息

代码语言:javascript
复制
arduino 代码解读复制代码Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

2.使用内存映像分析工具Jprofiler分析产生的堆储存快照 (1)我们可以先通过 top -c查看当前服务器进程并记录当前消耗cpu最高线程的pid。

比如发现当前线程pid为744的使用率最高。 (2)然后通过下面的命令到处jvm内存快照

代码语言:javascript
复制
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代码
代码语言:javascript
复制
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
代码语言:javascript
复制
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 秒钟,任务处理的速度远远跟不上任务提交的速度,导致任务队列越来越长。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Java 堆空间不足 (Java Heap Space)
    • 原因
      • 解决方案
        • Java 堆溢出排查解决思路
        • 2.线程栈空间不足 (Stack Overflow)
          • 原因
            • 解决方案
              • 栈溢出排查解决思路
                • demo代码
                • 3.方法区溢出
                  • 方法区溢出原因
                    • 方法区溢出排查解决思路
                    • 4.本机内存不足 (Native Memory Exhaustion)
                      • 原因
                        • 解决方案
                        • 5.GC 造成的内存不足 (GC Overhead Limit Exceeded)
                          • 原因
                            • 解决方案
                              • demo
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档