携手创作,共同成长!这是我参与「掘金日新计划 · 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代码或本地方法处,这种需要通过操作系统工具进行排查。
参考文档
Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation