OOM:由于java堆或本机内存中的内存耗尽而发生内存不足错误。在JVM中,当JVM由于堆内存不足而无法分配对象时,会抛出 OutOfMemoryError 错误,并且垃圾收集器无法提供更多的堆内存。

image

内存泄漏:如果应用程序正在使用内存,而应用程序在使用完内存后没有释放内存,则会发生内存泄漏。内存泄漏可能发生在java堆或本机内存中,并且最终会导致内存不足的情况。

故障排除

请注意,并非所有以下项目都需要完成。有些问题只能通过以下几个项目来解决。

故障排除步骤

Java堆、本机内存和进程大小

Java堆:这是JVM用来分配Java对象的内存。java堆内存的最大值是使用java命令行中的 -Xmx标志指定的。如果没有指定最大堆大小,那么这个限制是由JVM考虑机器中的物理内存量和此时可用的可用内存量等因素决定的。始终建议指定max java heap值。

本机内存:这是JVM用于自身内部操作的内存。JVM将使用的本机内存堆的数量取决于生成的代码数量、创建的线程数量、GC期间用于保存java对象信息的内存以及代码生成、优化等期间使用的临时空间。

如果有第三方本机模块,它也可以使用本机内存。例如,本机JDBC驱动程序分配本机内存。

本机内存的最大数量受任何给定操作系统上的虚拟进程大小限制以及已提交给带有 -Xmxflag的java堆的内存量的限制。例如,如果应用程序总共可以分配3 GB,如果最大java堆是1 GB,那么最大可能的本机内存大约是2 GB。

进程大小:进程大小将是java堆、本机内存以及加载的可执行文件和库所占用的内存的总和。在32位操作系统上,进程的虚拟地址空间最多可达4GB。在这个4GB中,OS内核为自己保留了一些部分(通常为1-2GB)。其余的可用于应用程序。

Windows:不同版本的Windows支持不同的进程大小。

redhat linux:RH-Linux上有不同的内核,这些不同的内核支持不同的进程大小。

对于其他操作系统,请参阅操作系统文档以了解您的配置。

有关为WebLogic服务器配置所有这些的更多信息,请参阅 调优Java虚拟机(jvm) 。

进程地址空间和物理内存之间的差异

每个进程都有自己的地址空间。在32位操作系统中,此地址空间的范围为0到4 GB。这与机器中可用的RAM或交换空间无关。由于jvm gc性能要求java堆的大部分位于RAM中,因此添加RAM比增加交换更有帮助尺寸。用于例如,对于一个使用8gig Java堆的Java应用程序,添加4gig的RAM加上12gig的swap并没有添加等量的纯RAM那么有用。

进程中的内存地址是虚拟的。内核将这个虚拟地址映射到物理地址。物理地址指向物理内存中的某个位置。在任何给定的时间,计算机中正在运行的进程使用的所有虚拟内存的总和都不能超过该计算机上可用的总物理内存。

为什么会出现OOM问题,JVM在这种情况下会做什么?

java堆内存不足

如果JVM不能在java堆中获得更多的内存来分配更多的java对象,JVM就会抛出java内存不足错误。如果java堆中充满了活动对象,并且JVM无法再扩展java堆,那么JVM将无法分配更多的java对象。

在这种情况下,JVM让应用程序决定在抛出 java.lang.OutOfMemoryError 错误. 例如,应用程序可能会处理此错误并决定以安全的方式关闭自身,或者决定忽略此错误运行。如果应用程序没有处理这个错误,那么抛出这个错误的线程将退出(如果进行java线程转储,您将看不到这个线程)。

在WebLogic服务器的情况下,如果这个错误是由一个execute线程抛出的,则会处理这个错误,并且会记录错误。如果连续抛出此错误,则核心运行状况监视器线程将关闭WebLogic服务器。

本机堆内存不足

如果JVM不能获得更多的本机内存,它就会抛出本机内存不足(native OOM)。这通常发生在进程达到该操作系统上的进程大小限制或计算机内存和交换空间不足时。

当这种情况发生时,JVM将处理本机OOM条件,记录一条消息,指出本机内存不足或无法获取内存并退出。如果JVM或任何其他加载的模块(如libc或第三方模块)不能处理这种本地OOM情况,那么OS将向JVM发送sigabort信号,使JVM退出。通常,JVM在收到SIGABROT信号时会生成一个核心文件。

调试问题的步骤

首先,确定它是java堆OOM还是本机OOM( 堆外 内存溢出

java.lang.OutOfMemoryError
java.lang.OutOfMemoryError

请注意,上面的消息转到stdout或stderr,而不是特定于应用程序的日志文件,如weblogic.log.

对于Java OOM:

收集并分析详细垃圾收集(GC)输出

启用详细GC日志记录。为了有效地记录GC活动,启动时JVM中应包括以下选项:

1. 对于HotSpot: -verbose:gc 、 -XX:+PrintGCDetails 和 -XX:+PrintGCTimeStamps 。 Xloggc :也可以指定将GC详细统计信息重定向到输出文件。除了日志文件消耗的一些磁盘空间之外,基本GC的开销是空的(有关更多详细信息,请参阅Java热点VM选项)。

2. 对于JRockit: -verbose:gc , gcpause , memdbg (有关详细信息,请参阅 JRockit命令行选项 )。

确保JVM在抛出java oom之前执行以下操作

完全GC运行:

执行一个完整的GC,所有不可到达的、幻象的、弱的和不可到达的对象都被移除,并且这些空间被回收。有关不同级别的对象可达性的更多详细信息,请访问: http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html ,参见“A.4.1参考对象类型”。

您可以检查是否在OOM消息之前完成了完全GC。当完成完整的GC时,会打印如下消息(格式因JVM而异:检查JVM帮助消息以了解格式)

[memory ] 7.160: GC 131072K->130052K (131072K) in 1057.359 ms

上述输出的格式如下(注意:整个模式将使用相同的格式):

[memory ] <start>: GC <before>K-><after>K (<heap>K), <total> ms
[memory ] <start> - start time of collection (seconds since jvm start)
[memory ] <before> - memory used by objects before collection (KB)
[memory ] <after> - memory used by objects after collection (KB)
[memory ] <heap> - size of heap after collection (KB)
[memory ] <total> - total time of collection (milliseconds)

但是,无法断定是否使用详细消息删除了软/弱/幻影可及对象。如果垃圾收集算法是分代算法(对于Jrockit,是gencopy或gencon,对于其他jdk,是默认算法),您还将看到详细的输出,如下所示:

[memory ] 2.414: Nursery GC 31000K->20760K (75776K), 0.469 ms

上面是托管GC(或年轻GC)循环,它将把活动对象从托管(或年轻空间)提升到旧空间。这个循环对于我们的分析并不重要。在JVM文档中可以找到关于时代算法的更多细节。

如果GC循环没有在java oom之前发生,那么它就是一个JVM错误。

完全压实:

确保JVM进行了适当的压缩工作,并且内存没有碎片化,这可能会阻止分配大型对象并触发java oom错误。

Java对象需要连续的内存。如果可用的空闲内存是碎片化的,那么JVM将无法分配一个大对象,因为它可能不适合任何可用的空闲内存块。在这种情况下,JVM应该进行完全压缩,以便形成更多连续的可用内存来容纳大型对象。

压缩工作涉及将对象(数据)从java堆内存中的一个位置移动到另一个位置,并更新对这些对象的引用以指向新位置。JVM可能不会压缩所有对象,除非有需要。这是为了减少GC循环的暂停时间。

我们可以通过分析详细的gc消息来检查javaoom是否是由于碎片造成的。如果您看到类似于下面的输出,其中抛出OOM,即使有可用的java堆,那么这是由于碎片造成的。

[memory ] 8.162: GC 73043K->72989K (131072K) in 12.938 ms
[memory ] 8.172: GC 72989K->72905K (131072K) in 12.000 ms
[memory ] 8.182: GC 72905K->72580K (131072K) in 13.509 ms
java.lang.OutOfMemoryError

在上面的例子中,您可以看到指定的最大堆是128MB,当实际内存使用量只有72580K时JVM抛出了OOM,堆使用率只有55%。因此,在这种情况下,碎片的效果是抛出OOM,即使有45%的空闲堆。这是一个JVM错误或限制。您应该联系JVM供应商。

如果JVM工作正常(以上步骤中提到的所有事情),那么java oom可能是一个应用程序问题。应用程序可能会不断泄漏一些java内存,这可能会导致此问题。或者,应用程序使用更多的活动对象,需要更多的java堆内存。可以在应用程序中检查以下内容:

在应用程序中缓存——如果应用程序在内存中缓存java对象,那么我们应该确保这个缓存不会不断增长。缓存中对象的数量应该有限制。我们可以尝试减少这个限制,看看它是否减少了java堆的使用。

Java软引用也可以用于数据缓存,因为当JVM耗尽Java堆时,可以保证软访问的对象被删除。

长寿命对象-如果应用程序中有长寿命对象,那么我们可以尽可能减少对象的寿命。例如,调整HTTP会话超时将有助于更快地回收空闲会话对象。

内存泄漏:内存泄漏的一个例子是在applicationserver中使用数据库连接池时。使用连接池时,必须在 finally 块中显式关闭JDBC语句和resultset对象。这是因为对池中的连接对象调用 close() 只会将连接返回到池中以供重用,而实际上不会关闭连接和关联的语句/结果集对象。

建议遵循以下文档中建议的编码实践,以避免应用程序中的内存泄漏。

  • JDBC-关闭JDBC对象* JNDI-关闭上下文* JMS-释放对象资源

增加java堆—如果可能的话,我们还可以尝试增加java堆,看看这是否解决了问题。

变通方法—作为一种临时变通方法,当java堆使用率达到90%左右时,应用程序可以正常地重新启动。遵循此解决方法时,可以将java max堆设置为尽可能高的值,以便应用程序需要更多的时间来填充所有java堆。可以通过在java命令行中添加' -verbosegc '标志(见上文)来监视java堆的使用情况,该标志将GC/堆使用情况信息发送到stdout或stderr。

如果上述建议都不适用于该应用程序,那么我们需要使用基于JVMPI(jvmprofiler Interface)的探查器来找出哪些对象正在占用java堆。探查器还提供了java代码中创建这些对象的位置的详细信息。本文档不包括每个探查器的详细信息。请参阅探查器文档,了解如何使用此探查器设置和启动应用程序。一般来说,基于JVMPI的探查器有很高的开销,并且大大降低了应用程序的性能。因此,不建议在生产环境中使用这些探查器。从这个站点可以浏览许多开源分析工具。

对于本机OOM问题

收集以下信息:

1. 启用详细GC日志记录(见上文)以监视java堆的使用情况。这将有助于理解此应用程序的java内存需求。

应该注意的是,与应用程序实际使用的java堆无关,指定的max heap量(在java命令行中使用 -Xmx 标志)是在JVM启动时保留的,并且该保留内存不可用于任何其他用途。

在JRockit的情况下,使用 -verbose 而不是 -verbosegc ,因为这除了提供GC信息外,还提供了codegen信息。

2. 从应用程序启动到JVM耗尽本机内存,定期记录进程虚拟内存大小。这将有助于了解进程是否真正达到了该操作系统的大小限制。

对于Windows,请使用以下过程监视虚拟进程大小:

1. 在开始->运行。。。对话框中,输入“perfmon”并单击“确定”。

2. 在弹出的“性能”窗口中,单击“+”按钮(在图表上方)。

3. 在生成的“添加计数器”对话框中选择以下选项:

  • 性能对象:进程(不是默认处理器)* 从列表中选择计数器:虚拟字节* selectinstancesfromlist:选择JVM(java)实例

4. 单击“添加”,然后单击“关闭”

对于Unix或Linux,对于给定的PID,可以使用以下命令找到虚拟内存大小: ps-p<PID>-ovsz。

在Linux中,单个JVM实例中的每个java线程都显示为一个单独的进程。如果我们采用根java进程的PID就足够了。根java进程可以使用ps命令的 --forest 选项找到。例如, ps-lU<user>--forest 将为指定用户启动的所有进程提供一个ASCII树。

计算机内存可用性

如果机器没有足够的RAM和交换空间,那么操作系统将无法为该进程提供更多内存,这也可能导致内存不足。确保磁盘中RAM和交换空间的总和足以满足该计算机中所有正在运行的进程的需要。

调整java堆

如果java堆的使用率在max堆内,那么减少java max堆将为JVM提供更多的本机内存。这不是一个解决方案,而是一个可以尝试的解决方法。由于操作系统限制了进程大小,我们需要在java堆和本机堆之间取得平衡。

应用程序中的第三方本机模块或JNI代码

检查是否正在使用任何第三方本机模块(如数据库驱动程序)。这些本机模块还可以分配本机内存,泄漏可能来自这些模块。为了缩小问题范围,您应该尝试在没有这些第三方模块的情况下再现问题。例如,可以使用纯java驱动程序而不是本机数据库驱动程序。

检查应用程序是否使用了一些JNI代码。这也可能导致本机内存泄漏,如果可能,您可以尝试在不使用JNI代码的情况下运行应用程序。

如果在上述步骤之后找不到本机内存的源,那么您需要与JVM供应商合作,以获得一个特殊的构建,该构建可以跟踪本机内存分配调用并提供有关泄漏的更多信息。

JVM内存泄露检查工具

以下URL提供了一些特定于HP JVM的OOM情况的工具和提示:HP JVM工具/提示

JRockit特定功能

JRockit支持JRA录制(Java运行时分析器)。这有助于在JVM运行时收集信息,这些信息将提供有关应用程序的信息,例如,正在运行的GC的数量、软/弱/虚引用的数量、热方法等。如果JVM有性能问题或挂起问题,则记录几分钟并分析数据是很有用的。

为了解决内存泄漏问题,我们建议在每次旧的收集之后进行飞行记录器录制 JFR 并检查堆的使用情况。持续上升的内存使用率可能表示内存泄漏。有关创建和解释飞行记录器录制的信息,请参阅《Oracle JRockit飞行记录器运行时指南》。

流行的JVM堆分析工具

Java VisualVM

JavaVisualVM是一个工具,它提供了一个可视化界面,用于查看Java应用程序在Java虚拟机(JVM)上运行时的详细信息,以及对这些应用程序进行故障排除和分析。Sun发行的Java开发工具包(JDK)提供了各种可选工具,包括Java VisualVM,用于检索有关运行JVM软件实例的不同类型的数据。例如,大多数以前独立的工具JConsole、jstat、jinfo、jstack和jmap都是javavisualvm的一部分。JavaVisualVM将这些工具联合起来,从JVM软件中获取数据,然后以图形方式重新组织和呈现信息,使您能够统一查看有关多个Java应用程序的不同数据,无论这些应用程序是在本地还是在远程机上运行。此外,开发人员可以通过创建插件并将其发布到工具的内置更新中心来扩展JavaVisualVM以添加新功能。

Java应用程序开发人员可以使用Java VisualVM对应用程序进行故障排除,并监视和改进应用程序的性能。JavaVisualVM允许开发人员生成和分析堆转储,跟踪内存泄漏,浏览平台的MBean并在这些MBean上执行操作,执行和监视垃圾收集,以及执行轻量级内存和CPU评测。

JavaVisualVM在JDKversion6,update7中首先和Java平台标准版(JavaSE)捆绑在一起。

有关更多信息,请参阅Java VisualVM。

JRockit内存泄漏检测器

JRockit内存泄漏检测器是一种工具,用于发现和查找Java应用程序中内存泄漏的原因。JRockit内存泄漏检测器的趋势分析器发现了缓慢的泄漏,它显示了详细的堆统计信息(包括引用泄漏对象的类型和实例)、分配站点,并提供了对内存泄漏原因的快速深入分析。内存泄漏检测器使用先进的图形表示技术,使它更容易导航和理解有时复杂的信息。有关更多详细信息,请参阅内存泄漏检测入门。

Eclipse内存分析工具(MAT)

独立内存分析工具(MAT)基于eclipsercp。如果您不想在运行堆分析的系统上安装一个完整的IDE,那么它非常有用。

如果觉得本文对你有帮助,可以点赞关注支持一下