Java内存溢出(OOM)原因分析与解决方法:堆溢出、内存泄露排查指南
前言

撸 Java 的同学,会在一定程度上碰到内存溢出(OOM)的情况。而导致 OOM 出现的原因是各不相同的,有着多种形式。
堆溢出
这种场景是最为常见的。报错信息为:java.lang.OutOfMemoryError: Java heap space。
原因
1、代码中可能存在大对象分配
可能会有内存泄露的情况,这会使得在经过多次垃圾回收(GC)之后,依然无法找到一块足够大的内存来容纳当前的对象。
1、检查是否存在大对象的分配,最有可能的是大数组分配
使用 jmap 命令将堆内存进行 dump 操作,然后利用 mat 工具对其进行分析,以此来检查是否存在内存泄露的问题。
3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存
还有一点容易被忽视,需要检查是否存在大量的自定义的 Finalizable 对象,这些对象有可能是框架内部提供的,要考虑它们存在的必要性。
永久代/元空间溢出
报错信息:
java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace
原因
HotSot 虚拟机的永久代是对方法区的具体实现。它存放了被虚拟机加载的类信息,还存放了常量,同时也存放了静态变量,并且存放了 JIT 编译后的代码等。
JDK8 之后,永久代被元空间所替换。元空间使用的是本地内存。同时还有其他的一些细节发生了变化。
字符串常量由永久代转移到堆中和永久代相关的JVM参数已移除
可能原因有如下几种:
在 Java7 之前,经常会错误地使用 String.intern()方法。
2、运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
应用长时间运行,且没有重启 JVM 进程的情况通常发生在调试期间,就像下面 tomcat 官网的一个常见问题解答(FAQ)中所描述的那样:
?.一个常见的问题是“PermGen”内存泄漏。.它们之所以发生,是因为类加载器。(and the Class objects it loaded)只有满足一些要求,才能进行回收。()., and when you redeploy a new class loader is created,它加载了所有这些类的另一份副本。.. (*)this.
解决方法
因为该OOM原因比较简单,解决方法有如下几种:
1、检查是否永久代空间或者元空间设置的过小
2、检查代码中是否存在大量的反射操作
dump 之后,要检查是否存在大量由于反射生成的代理类,可通过 mat 进行检查。
4、放大招,重启JVM
我在这里分享一些内容,其中包含很多实用的知识,有关于 jvm、netty、spring、线程、spring cloud 等方面的详细讲解,还有详细的学习规划图以及面试题整理等。我觉得在面试这方面讲解得十分清晰。如果想要获取面试资料,只需点击这里领取!暗号:CSDN。
GC overhead limit exceeded
这个异常比较少见,其报错信息为:java.lang.OutOfMemoryError:GC overhead limit exceeded。
原因
JDK6 新增了这种错误类型,通常是因为堆太小所导致的。Sun 官方对其的定义为:当超过 98%的时间都用于做 GC,并且回收的堆内存不到 2%时,就会抛出此异常。
解决方法
对代码进行优化。
添加参数 -XX:-UseGCOverheadLimit 来禁用这个检查。实际上,这个参数无法解决内存问题,仅仅是将错误信息延后了,最终会出现 java.lang.OutOfMemoryError: Java heap space 这种情况。
dump 内存,接着检查是否有内存泄露的情况。若没有发现内存泄露,就将内存加大。
方法栈溢出
报错信息表明出现了“java.lang.OutOfMemoryError”错误,具体情况是无法创建新的本地线程。
原因
出现这种异常,通常是因为创建了大量的线程。之前曾碰到过一次类似情况,通过 jstack 查看,一共出现了 8000 多个线程。
解决方法
1、通过 -Xss 降低的每个线程栈大小的容量
线程总数会受到系统空闲内存以及操作系统的限制。需要检查在该系统下是否存在这样的限制。
/proc/sys/kernel/pid_max
/proc/sys/kernel/thread-max 是最大用户进程数(ulimit -u)。
/proc/sys/vm/maxmapcount
非常规溢出
这些 OOM 异常,可能大部分同学未曾碰到过,不过依然需要对其有所了解。
分配超大数组
报错信息表明出现了“java.lang.OutOfMemoryError”的情况,具体是因为请求的数组大小超出了虚拟机的限制。
这种情况通常是由不合理的数组分配请求所引起的。在给数组分配内存之前,JVM 会进行一项检查。即要分配的数组在该平台上是否能够被寻址,如果不能被寻址,就会抛出这个错误。
解决方法就是检查你的代码中是否有创建超大数组的地方。
swap溢出
报错信息表明出现了“java.lang.OutOfMemoryError”,具体原因是“Out of swap space”,即超出了交换空间。
这种情况一般是操作系统导致的,可能的原因有:
1、swap 分区大小分配不足;
2、其他进程消耗了所有的内存。
解决方案:
也可以加大机器的内存大小。
本地方法溢出
报错信息为:java.lang.OutOfMemoryError ,具体内容为 stack_trace_with_native_method 。
本地方法在运行期间发生了内存分配失败。之前的方法是栈溢出,两者不同。方法栈溢出出现在 JVM 代码层面,而本地方法溢出则出现在 JNI 代码或本地方法处。
这个异常出现的概率非常低。只能借助操作系统的本地工具来进行诊断,而且难度比较大。所以还是放弃比较好。
最后
我在这里分享一个,有很多干货,包含jvm,netty,spring,线程,spring cloud等详细讲解,也有详细的学习规划图,面试题整理等,我感觉在面试这块讲的非常清楚:获取面试资料只需:点击这里领取!!! 暗号:CSDN