Java产生OOM的几种类型,解决方案与步骤

说明:
发生OOM后,查看程序日志可以获取到发生OOM的类型,也就是下面所列出的打印出的提示信息。然后根据 GC日志(通过 -Xloggc:file:指定文件位置)、OOM日志(-XX:+HeapDumpOnOutOfMemoryError:发生OOM时dump出内存信息)、dump出内存信息、结合代码来定位。

1. java heap space:

堆发生OOM很常见,一般分为两种情况,内存泄漏与堆内存确实存放不下:
1) 内存泄漏:
内存泄漏就是不再使用的内存任然保持着强引用,导致无法被GC掉。
导致原因一般是使用流操作、连接操作后未释放;集合使用不当造成。属于代码逻辑错误。
解决办法:
使用 jmap -histo:live pid 打印出堆中对象的统计报告(可以得到每个对象的数量、占用的内存),查看最耗内存的对象有哪些。然后使用jmap dump:format=b,file=/aa/dumpFile dump出内存快照,再使用 jhat -port 端口号 /aa/dumpFile进行分析。
除了使用是上面的命令,使用jdk提供的 JVisual VM可以同样完成上面操作,还很直观。dump出堆内存后,以报表的形式统计每个类的实例数、内存占用。选择实例还能查看其该实例的引用。

2)未发生内存泄漏,堆内存大小不够:
这种情况发生的原因有:堆内存本身设置太小、大对象的分配(查询一个大表未分页,直接一个放进一个List)、流量飙升(秒杀等场景未做限流)、代码逻辑问题导致大量产生对象(代码质量不高,可以使用享元模式进行对象复用)。

这种情况,可以使用 jmap -heap查看堆的分配与使用情况,若确实太小,则适当增大内存。大对象追踪与上面内存泄漏步骤相似。

2. unable to create new native thread:

这种情况是由于创建过多的线程而造成,每一个线程都对应一个Java虚拟机栈,大小由-Xss指定(默认是1M)。系统创建线程时,除了要在Java堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。
发现该错误时,先看是否存在在循环中不断创建线程,这里要具体查看代码。若代码确实需要这么多的线程,此时可以根据 【JVM总内存 - 堆 = n*Java虚拟机栈 】,来减小堆的内存或者Xss来解决增加可分配线程的数量。

3. Metaspace:

这里导致的原因主要是 大量类的生成。一般是由于动态代理、反射的使用动态生成大量的类。
解决办法还是,观察类的占用情况,dump出内存,定位到代码检查代码实现是否合理。若是发现代码没什么问题,则使用-XX:MaxMatesapceSize 调整metaspace的大小。

4. Direct buffer memory:

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

Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,定位到ByteBuffer.allocateDirect 操作即可(使用NIO的地方,如 netty,jetty )。
直接内存由 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。如果确实是分配内存过小,则通过参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值。

5. GC overhead limit exceeded:

当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。

该情况的诊断与java heap space一样。

猜你喜欢

转载自blog.csdn.net/qq_40728028/article/details/106348187