Java 常见的几种 OOM
1、StackOverflowError(栈空间溢出)
上面这种 OOM 比较好理解,在 main 方法中循环调用 main 方法,循环产生的大量形参都会在栈空间进行创建,当超过栈空间的大小,就会导致栈空间溢出,发生 OOM。
2、Java Heap Space(堆空间溢出)
上面的这个 OOM 也比较好理解,我给 JVM 设置的初始化堆内存和最大堆内存大小都是 10M,然后我在 main 方法中创建一个 20M 大小的字节数组,很显然一下子就超过堆内存大小了,直接发生 OOM。
3、GC overhead limit exceeded(GC 回收时间过长)
GC 回收时间过长时会抛出 OutOfMemoryError 。过长的定义是,超过 98% 的时间用来做 GC ,并且回收了不到 2% 的堆内存,连续多次 GC 都只回收了不到 2% 的极端情况下才会抛出。假如不抛出 GC overhead limit 错误会发生什么情况呢?
那就是 GC 清理的这么点内存很快会再次填满,迫使 GC 再次执行,这样就形成恶性循环,CPU 使用率一直是 100%,而 GC 却没有任何成果。
4、Direct buffer memory(本机直接内存溢出)
1️⃣ 写 NIO 程序经常使用 ByteBuffer
来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式。它可以使用 Native 函数库直接分配堆外内存,然后通过一个存在在 Java 堆里面的 DirectByteBuffer
对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
2️⃣ ByteBuffer.allocate(capability)
第一种方式是分配 JVM 堆内存,属于 GC 管辖范围,由于需要拷贝所以速度相对较慢。
3️⃣ ByteBuffer.allocateDirect(capability)
第二种方式是分配 OS 本地内存,不属于 GC 管辖范围,由于不需要内存拷贝,所以速度相对较快。
4️⃣ 但如果不断分配本地内存,堆内存很少使用,那么 JVM 就不需要执行 GC ,DirectByteBuffer 对象们就不会被回收,这时候堆内存充足,但本地内存可能已经用光了,再次尝试分配本地内存就会出现 OutOfMemory ,程序直接崩溃。
Tips:
1、可以使用
-XX:MaxDirectMemorySize
来指定本机直接内存的大小2、在 NIO 程序中,使用
ByteBuffer.allocateDirect(capability)
分配的是直接内存,可能会导致堆内存溢出。
5、unable to create new native thread(不能创建一个本地线程)
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unable to create new native thread
准确的说 native thread 异常与对应的平台有关。
导致原因:
你的应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
你的服务器并不允许你的应用程序创建这么多线程,linux 系统默认允许单个进程可以创建的线程数是 1024 个,你的应用创建超过这个数量,就会报
java.lang.OutOfMemoryError:unable to create new native thread
解决办法:
想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,修改代码将线程数降到最低
对于有的应用,确实需要创建很多的线程,远超过 linux 系统的默认 1024 个线程的限制,可以通过修改 linux 服务器配置,扩大 linux 默认限制
通过扩大服务器线程限制解决方法:
版权声明: 本文为 InfoQ 作者【hepingfly】的原创文章。
原文链接:【http://xie.infoq.cn/article/c825fe3c6973818848c2a9e32】。文章转载请联系作者。
评论