环境:Java8
1. 简介
内存泄漏的一个常见迹象是 java.lang.OutOfMemoryError 异常。通常,当 Java 堆中没有足够的空间分配对象时,就会抛出该错误。在这种情况下,垃圾回收器无法腾出空间容纳新对象,堆也无法进一步扩展。此外,当本地内存不足以支持 Java 类的加载时,也可能抛出此错误。在极少数情况下,当垃圾回收耗时过长而释放的内存很少时,也可能会抛出 java.lang.OutOfMemoryError 错误。
当抛出 java.lang.OutOfMemoryError 异常时,也会打印堆栈跟踪。
当本地分配无法满足(例如交换空间不足)时,本地库代码也会抛出 java.lang.OutOfMemoryError 异常。
诊断 OutOfMemoryError 异常的第一步是确定异常的原因。抛出异常的原因是 Java 堆已满,还是本地堆已满?为了帮助您找到原因,异常文本的末尾会包含一条详细信息,如以下异常所示。
接下来将介绍每一种OOM的情况
2. OOM汇总
2.1 OOM: Java heap space
原因:Java堆空间详细信息表明无法在Java堆中分配对象。此错误并不一定意味着存在内存泄漏。问题可能很简单,仅仅是由于配置问题,即应用程序指定的堆大小(如果没有指定,则为默认大小)不足。在其他情况下,特别是对于长期运行的应用程序,该信息可能表明应用程序无意中持有对象的引用,这导致对象无法被垃圾回收。这是Java中相当于内存泄漏。注意:应用程序调用的API也可能无意中持有对象的引用。
这个错误的另一个潜在来源出现在那些过度使用终结器的应用程序。如果一个类有一个终结(finalize)方法,那么在该类型的对象进行垃圾回收时,它们的空间并不会被立即回收。相反,在垃圾回收之后,这些对象会被排队等待终结,这个过程会在稍后进行。在Oracle Sun的实现中,终结器(finalize)是由一个守护线程执行的,该线程服务于终结队列。如果终结器线程无法跟上终结队列的处理速度,那么Java堆可能会填满,从而抛出这种类型的OutOfMemoryError异常。导致这种情况的一个场景是,应用程序创建了高优先级的线程,这些线程导致终结队列的增长速度超过了终结器线程处理该队列的速度。
解决方案:增加堆大小;通过相关的工具如jmap导出当前堆内存快照(在启动程序前添加),如下配置:
// 启动程序时指定OOM相关参数以生成dump文件-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/jvm/dump/xxx.hprof// 增加堆内存-Xms4g -Xmx4g
你导出了hprof文件后,你可以通过MAT工具进行内存泄漏的分析。
2.2 OOM: GC Overhead limit exceeded
原因:“GC overhead limit exceeded”的详细消息表明垃圾收集器一直在运行,而Java程序进展非常缓慢。在垃圾收集之后,如果Java进程花费超过大约98%的时间进行垃圾收集,并且如果它只恢复了堆内存的不到2%,并且这种情况在最近的5次(编译时常量)连续垃圾收集中一直存在,那么将抛出java.lang.OutOfMemoryError。这个异常通常是因为存活的数据量几乎占满了Java堆,为新分配留下的空闲空间很少。
解决方案:增加堆内存大小。可以使用命令行标志-XX:-UseGCOverheadLimit来关闭因GC开销限制超出而抛出的java.lang.OutOfMemoryError异常。
2.3 超限:Requested array size exceeds VM limit
原因:"Requested array size exceeds VM limit"表明应用程序(或该应用程序使用的API)试图分配一个大小超过虚拟机实现限制的数组,而不管堆内存大小有多少。
解决方案:确保你的应用程序(或该应用程序使用的API)分配一个大小小于虚拟机实现限制的数组。
如下示例,将抛出上面的错误
for (int i = 3; i >= 0; i--) {try {System.out.printf("申请数组长度:%d%n", (Integer.MAX_VALUE - i)) ;int[] temp = new int[Integer.MAX_VALUE - i] ;} catch (Throwable t) {t.printStackTrace() ;}}
控制台输出

前2次输出的是Java heap space OOM错误,这是因为你申请2^31- N长度的int数组需要至少8G的内存空间,这远远大于默认的堆内存,所以抛出该错误。
2.4 OOM:Metaspace
原因:Java类元数据被分配在本地内存(这里称为元空间)中。如果用于类元数据的元空间耗尽,将抛出带有“MetaSpace”的java.lang.OutOfMemoryError异常。可用于类元数据的元空间量受到命令行上指定的MaxMetaSpaceSize参数的限制。当某个类元数据所需的本地内存量超过MaxMetaSpaceSize时,将抛出一个带有详细信息“MetaSpace”的java.lang.OutOfMemoryError异常。
解决方案:如果已在命令行上设置了MaxMetaSpaceSize,请增加其值。元空间与Java堆分配自相同的地址空间。减少Java堆的大小将使更多空间可用于元空间。这只有在Java堆中存在过多空闲空间时才是正确的权衡选择。
2.5 OOM: request size bytes for reason. Out of swap space?
原因:详细消息"request size bytes for reason. Out of swap space?"看似是一个OutOfMemoryError异常。然而,当从本地堆分配内存失败且本地堆可能接近耗尽时,Java HotSpot VM代码会报告这个明显的异常。该信息指出了失败请求的大小(以字节为单位)以及内存请求的原因。通常,原因是报告分配失败的源模块的名称,尽管有时它也可能是实际原因。
解决方案:当抛出此错误消息时,VM会调用致命错误处理机制(即生成一个致命错误日志文件,其中包含崩溃时线程、进程和系统的有用信息)。在本地堆耗尽的情况下,日志中的堆内存和内存映射信息可能很有用。如果抛出这种类型的OutOfMemoryError异常,你可能需要使用操作系统上的故障排除工具来进一步诊断问题。有关各种操作系统上可用工具的更多信息,请参阅原生操作系统工具。
2.6 OOM: Compressed class space
原因:在64位平台上,类元数据的指针可以通过32位偏移量来表示(使用UseCompressedOops)。这是由命令行标志UseCompressedClassPointers(默认启用)控制的。如果启用了UseCompressedClassPointers,则可用于类元数据的空间量被固定在CompressedClassSpaceSize所指定的量上。如果UseCompressedClassPointers所需的空间超过CompressedClassSpaceSize,则会抛出一个带有详细信息“Compressed class space”的java.lang.OutOfMemoryError异常。
解决方案:增加CompressedClassSpaceSize的值或关闭UseCompressedClassPointers。请注意:CompressedClassSpaceSize的可接受大小是有限制的。例如,-XX:CompressedClassSpaceSize=4g,超出可接受范围将导致类似以下消息的错误:
“CompressedClassSpaceSize的值为4294967296是无效的;它必须在1048576和3221225472之间。”
2.7 OOM: reason stack_trace_with_native_method
原因:如果错误消息的详细信息部分是“reason stack_trace_with_native_method”,并且打印的堆栈跟踪中顶部帧是一个本地方法,那么这表明一个本地方法遇到了内存分配失败。这与之前的消息的区别在于,内存分配失败是在Java本地接口(JNI)或本地方法中检测到的,而不是在JVM代码中。
解决方案:如果抛出了这种类型的OutOfMemoryError异常,你可能需要使用操作系统的本地工具来进一步诊断问题。如:DTrace Tool。
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
推荐文章
优雅重构Spring Boot代码,我用这6种策略消灭if else
强大!SpringBoot通过这3个注解监测Controller接口
SpringBoot自带Controller接口监控,赶紧用起来
强大的异步任务处理类CompletableFuture使用详解



