详解 JVM OutOfMemoryError 第三篇

172 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

Java.Lang.OutOfMemoryError:GC overhead limit exceeded

Java 运行时环境包含一个内置的垃圾收集(GC)进程,Java 应用程序仅需要分配内存。每当不再使用内存中的特定空间时,称为垃圾收集的单独进程就会为它们清除内存。

在显示 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误信息时表示应用程序已经耗尽了几乎所有的可用内存,并且 GC 一直未能回收它。

原因:Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。

解决方案

大体上与Java heap相关问题解决有关。

  • 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
  • 检查JVM参数-Xmx -Xms是否合理
  • dump内存,检查是否存在内存泄露,如果没有,加大内存。

直接内存溢出

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使用,而且也可能导致OOM。

在JDK1.4 中新加入了NIO(New Input/Output)类,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

内存溢出原因

  • 本机直接内存的分配虽然不会受到Java 堆大小的限制,但是受到本机总内存大小限制。
  • 直接内存由 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。
  • NIO程序中,使用ByteBuffer.allocteDirect(capability)分配的是直接内存,可能导致直接内存溢出。

排查思路

  • 检查代码是否恰当
  • 检查代码中直接或间接使用了NIO,如Netty、Jetty等
  • 检查JVM参数-Xmx,-XX:MaxDirectMemorySize 是否合理。
  • 内存容量不足进行升级

举个例子

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

import java.nio.ByteBuffer;
public class DirectBufferMemory {
    public static void main(String[] args) throws InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*200);
        System.out.println("result");
    }

}

结果

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
        at java.nio.Bits.reserveMemory(Bits.java:694)
        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
        at org.jvm.DirectBufferMemory.main(DirectBufferMemory.java:14)

java.lang.OutOfMemoryError: unable to create new native thread

每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。java虚拟机中有虚拟机栈和本地方方法栈,通常存在两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常
  • 如果虚拟机栈扩展时,比如新建线程无法申请到足够的内存时会抛出java.lang.OutOfMemoryError: unable to create new native thread。

原因分析

JVM 向 OS 请求创建 native 线程失败,就会抛出 Unableto create new nativethread,常见的原因包括以下几类:

  • 不断地建立线程的方式会导致内存溢出。
  • 线程数超过操作系统最大线程数 ulimit 限制;
  • 线程数超过 kernel.pid_max(只能重启);
  • native 内存不足;

排查思路

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

解决方案

1、升级配置,为机器提供更多的内存;

2、降低 Java Heap Space 大小;

3、修复应用程序的线程泄漏问题;

4、限制线程池大小;

5、使用 -Xss 参数减少线程栈的大小;

6、调高 OS 层面的线程最大数:执行 ulimia-a 查看最大线程数限制,使用 ulimit-u xxx 调整最大线程数限制。

案例

如下,不停的去创建线程,导致的OOM


public class StackOOM {
    public static void main(String[] args) {
        while (true){
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException exception) {
                        exception.printStackTrace();
                    }
                }
            });
            thread.start();
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:717)
        at org.jvm.StackOOM.main(StackOOM.java:36)

StackOverflowError 代码递归调用方法:


public class StackOOM {
    public static void main(String[] args) {
        stackOomO();
    }
    private static void stackOomO() {
        stackOomO();
    }
 }
Exception in thread "main" java.lang.StackOverflowError
        at org.jvm.StackOOM.stackOomO(StackOOM.java:30)
        at org.jvm.StackOOM.stackOomO(StackOOM.java:30)

其他OOM异常

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

不合理的数组分配请求导致,一般是申请了超大的数组,如果遇到这个错误需要查看代码当中是否有创建超大数组的地方。

Swap 溢出Java.lang.OutOfMemoryError : Out of swap space

一般是操作系统导致的,该错误表示所有的可用内存已被耗尽,虚拟内存由物理内存和交换空间(Swap space)两部分组成,当运行程序请求的虚拟内存溢出时就会报Out of swap space,预计的原因有:1、swap分区大小分配不足2、其它进程消耗了所有的内存。解决方法就是对症下药,加大swap分区或者加大机器内存,其他进程服务拆到其他机器上部署。

本地方法溢出

java.lang. OutOfMemoryError: stack_trace_with_native_method 方法栈溢出发生在 JVM 代码层面,而本地方法溢出发生在JNI代码或本地方法处,这种需要通过操作系统工具进行排查。

参考文档

blog.csdn.net/allenjsl/ar…

segmentfault.com/a/119000002…

Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation

cloud.tencent.com/developer/a…

juejin.cn/post/690866…

zhuanlan.zhihu.com/p/95150243