如果应用程序的执行时间越来越长,或者操作系统的执行速度越来越慢,这可能是内存泄漏的迹象。换句话说,正在分配虚拟内存,但在不再需要时不会返回。最终应用程序或系统内存不足,应用程序异常终止。

使用Java飞行记录器调试内存泄漏

Java飞行记录器(JFR)是一个商业特性。您可以在开发人员台式机或笔记本电脑上免费使用它,也可以在测试、开发和生产环境中用于评估目的。

但是,要在生产服务器上启用JFR,必须具有商业许可证。在JDK上为其他目的使用Java任务控制(JMC)不需要商业许可证。

下面的部分展示了图并描述了如何使用Java飞行记录器调试内存泄漏。

检测内存泄漏

使用Java飞行记录尽早检测内存泄漏并防止内存不足错误。

检测缓慢的内存泄漏可能很困难。一个典型的症状是,由于频繁的垃圾回收,应用程序在长时间运行后会变慢。最终,可能会看到OutOfmemoryErrors。然而,使用Java飞行记录可以及早发现内存泄漏,甚至在问题发生之前。

观察应用程序的实时集是否随着时间的推移而增加。live set是旧集合(所有非活动对象都已被垃圾回收)之后使用的Java堆量。可以通过多种方式检查活动集:使用-verbosegc选项运行,或者使用jmc jmx控制台连接到JVM并查看 com.sun.management.GarbageCollectorAggregator MBean。然而,另一个简单的方法是进行飞行记录。

启动录制时启用堆统计信息,这将在录制开始和结束时触发旧集合。这可能会导致应用程序稍有延迟。但是,堆统计信息生成准确的活动集信息。如果您怀疑内存泄漏的速度相当快,那么可以进行一个分析记录,例如,一个小时。单击Memory选项卡并选择Garbage Collections选项卡以检查第一个和最后一个旧集合,如图所示:

java 内存泄漏检查 java 内存泄漏排查工具_jvm


编辑切换为居中

添加图片注释,不超过 140 字(可选)

选择第一个旧集合,如图所示,以查看GC之后的堆数据和堆使用情况。在图中,它是34.10 MB。现在,查看列表中最后一个旧集合中的相同数据,看看活动集是否增长了。在录制之前,必须允许应用程序启动并达到稳定状态。

如果泄漏很慢,你可以用较短的5分钟录音。然后,再录一次,例如24小时后(取决于你怀疑内存泄漏的速度)。显然,你的实时设置可能会有上有下,但是如果你看到一个稳定的增长,那么你可能会有内存泄漏。

找到内存泄漏的类

使用Java飞行记录来识别内存泄漏。

在记录显示泄漏后,可以查看对象统计信息。看一个长的记录,然后看看哪些类的堆使用量比记录增长得最快。如果您每隔一段时间记录几次,那么比较堆内容部分,看看哪些对象类型在这些记录之间增加得最多,如图所示:

java 内存泄漏检查 java 内存泄漏排查工具_jvm_02


编辑切换为居中

添加图片注释,不超过 140 字(可选)

尤其要注意那些不属于标准库的类。例如,您经常会看到Char数组是一个顶级的增长对象。这是由于分配了许多字符串;因此,请注意保持这些字符串活动的对象。如果您的类有10个字符串作为成员,那么对象本身不会使用太多堆。堆将由字符串使用,字符串主要包含指向Char数组的指针。因此,最好根据实例的数量而不是对象的大小进行排序。如果某个应用程序内有多个实例,则可能是这些对象使其他对象保持活动状态。

找到漏洞

使用Java飞行记录的附加信息识别内存泄漏的提示。

使用Java飞行记录可以找到一些附加信息。

查看Allocations的选项卡,如图所示,以获取对象分配位置的一些示例。

java 内存泄漏检查 java 内存泄漏排查工具_java_03


编辑切换为居中

添加图片注释,不超过 140 字(可选)

如果排除特定类泄漏,请查看新TLAB选项卡中的分配。检查正在分配的类样本。如果泄漏速度很慢,则可能有一些此对象的分配,并且可能没有样本。此外,可能只有特定的分配站点才会导致泄漏。总而言之,这并不能保证为泄漏找到正确的分配堆栈跟踪,但它可能会提供重要的线索。

了解OutOfMemoryError异常

java.lang.OutOfMemoryError当没有足够的空间在Java堆中分配对象时抛出错误。

内存泄漏的一个常见迹象是 java.lang.OutOfMemoryError例外情况。在这种情况下,垃圾回收器无法腾出空间来容纳新对象,堆也无法进一步扩展。此外,当本机内存不足,无法支持Java类的加载时,可能会抛出此错误。在极少数情况下 java.lang.OutOfMemoryError在执行垃圾收集的时间过长,并且释放的内存很少时,会引发。

当 java.lang.OutOfMemoryError抛出异常时,也会打印堆栈跟踪。

这个 java.lang.OutOfMemoryError当无法满足本机分配时(例如,如果交换空间很低),本机库代码也会引发异常。

诊断OutOfMemoryError异常的早期步骤是确定异常的原因。它是因为Java堆已满,还是因为本机堆已满而抛出?为了帮助您找到原因,异常的文本在末尾包含一条详细消息,如以下异常所示。

线程名中出现异常:Java.Lang.OutOfMemoryError:Java Heap Space

原因:详细的消息Java heap space指示无法在Java堆中分配对象。此错误不一定意味着内存泄漏。问题可以简单到配置问题,指定的堆大小(或默认大小,如果未指定)不足以用于应用程序。

在其他情况下,尤其是对于长生存期的应用程序,消息可能表示应用程序无意中保留了对对象的引用,这可以防止对象被垃圾回收。这是相当于内存泄漏的Java语言。注意:应用程序调用的api也可能无意中保存了对象引用。

此错误的另一个潜在来源是过度使用终结器的应用程序。如果类具有finalize方法,则该类型的对象在垃圾收集时不会回收其空间。取而代之的是,在垃圾回收之后,这些对象将排队等待最后确定,这将在以后发生。Sun中的服务终结器是由Oracle后台程序执行的线程终结器。如果终结器线程跟不上终结队列,那么Java堆可能会填满,并引发这种类型的OutOfMemoryError异常。可能导致这种情况的一种情况是,当应用程序创建高优先级线程时,这些线程会导致终结队列以高于终结器线程为该队列提供服务的速率增加。

操作:若要了解有关如何监视终结挂起的对象的详细信息,请监视挂起终结的对象。

线程线程名中出现异常:Java.Lang.OutOfMemoryError: GC Overhead Limit Exceeded 超出GC限制

原因:详细消息“GC overhead limit exceeded”表示垃圾回收器一直在运行,而Java程序的进度非常缓慢。在垃圾收集之后,如果Java进程花费了大约98%的时间进行垃圾收集,并且它恢复的堆不到2%,并且在最近5次(编译时常数)连续的垃圾收集中一直这样做,那么 java.lang.OutOfMemoryError被抛出。此异常通常会引发,因为活动数据量几乎不能放入Java堆中,因为Java堆中几乎没有用于新分配的可用空间。

操作:增加堆大小。这个 java.lang.OutOfMemoryError可以使用命令行标志-XX:-usegcoveredlimit关闭超出GC开销限制的异常。

线程线程名中出现异常:Java.Lang.OutOfMemoryError:请求的数组大小超过VM限制

原因:详细信息“请求的数组大小超过VM限制”表示应用程序(或该应用程序使用的API)试图分配大于堆大小的数组。例如,如果应用程序试图分配512 MB的数组,但最大堆大小为256 MB,则会抛出OutOfMemoryError,并给出“请求的数组大小超过VM限制”的原因

操作:通常问题是配置问题(堆大小太小)或导致应用程序试图创建一个大数组的错误(例如,当使用计算错误大小的算法计算数组中的元素数时)。

线程线程名中出现异常:Java.Lang.OutOfMemoryError:Metaspace 元空间

原因:Java类元数据(Java类的虚拟机内部表示)分配在本机内存中(这里称为元空间)。如果类元数据的元空间用尽,则 java.lang.OutOfMemoryError引发具有detail元空间的异常。可用于类元数据的元空间量受参数MaxMetaSpaceSize的限制,该参数在命令行中指定。当类元数据所需的本机内存量超过MaxMetaSpaceSize时,一个 java.lang.OutOfMemoryError引发具有detail元空间的异常。

操作:如果在命令行上设置了MaxMetaSpaceSize,请增加其值。元空间是从与Java堆相同的地址空间分配的。减小Java堆的大小将为MetaSpace提供更多的可用空间。只有在Java堆中有多余的可用空间时,这才是一个正确的权衡。有关交换空间不足的详细信息,请参阅以下操作。

线程线程名中出现异常:Java.Lang.OutOfMemoryError:请求大小字节的原因。交换空间不足?

原因:详细信息“请求大小字节原因。交换空间不足?”似乎是OutOfMemoryError异常。但是,当本机堆的分配失败并且本机堆可能接近耗尽时,Java hotspotsvm代码会报告这个明显的异常。该消息指示失败的请求的大小(以字节为单位)以及内存请求的原因。通常原因是报告分配失败的源模块的名称,尽管有时这是实际原因。

操作:当抛出此错误消息时,VM调用致命错误处理机制(即,它生成一个致命错误日志文件,其中包含有关崩溃时线程、进程和系统的有用信息)。在本机堆耗尽的情况下,日志中的堆内存和内存映射信息可能很有用。请参阅致命错误日志。

如果引发这种类型的OutOfMemoryError异常,则可能需要使用操作系统上的故障排除实用程序来进一步诊断该问题。

线程线程名中出现异常:Java.Lang.OutOfMemoryError:压缩的类空间

原因:在64位平台上,指向类元数据的指针可以用32位偏移量表示(使用UseCompressedOops)。这由命令行标志 UseCompressedClassPointers控制(默认情况下启用)。如果使用 UseCompressedClassPointers,则类元数据的可用空间量固定为CompressedClassSpaceSize。如果 UseCompressedClassPointers所需的空间超过CompressedClassSpaceSize,则 java.lang.OutOfMemoryError使用detail压缩类空间。

操作:增加“CompressedClassSpaceSize”以禁用“ UseCompressedClassPointers”。注意:CompressedClassSpaceSize的可接受大小有界限。例如 -XX:CompressedClassSpaceSize=4g,超出可接受的界限将导致以下消息:




CompressedClassSpaceSize of 4294967296 is invalid; must be between 1048576 and 3221225472.

注意:

有不止一种类元数据,即klass元数据和其他元数据。只有klass元数据存储在由CompressedClassSpaceSize限定的空间中。其他元数据存储在Metaspace中。

线程线程名中出现异常:Java.Lang.OutOfMemoryError:reason Stack_trace_with_native_方法

原因:如果错误消息的详细信息部分是“reason stack_trace_with_native_method”,并且打印的堆栈跟踪的顶部框架是本机方法,则这表示本机方法遇到了分配失败。这条消息与前一条消息的区别在于,分配失败是在Java本机接口(JNI)或本机方法中检测到的,而不是在JVM代码中检测到的。