本文大纲

《JVM剖析及性能跟踪》

实践篇:当 JVM 内存溢出时的回调处理

正文

在 Java 应用开发中,内存溢出(OutOfMemoryError)是一个常见的问题,它会导致应用性能下降,甚至崩溃。及时有效地处理内存溢出问题,是每个 Java 开发者和运维人员必备的技能。本文将介绍 JVM 提供的几个处理内存溢出的参数,并结合实际案例,讲解如何在内存溢出时进行回调处理,帮助你快速定位和解决内存问题。

目标

  • 了解 JVM 处理内存溢出的相关参数
  • 掌握在内存溢出时进行回调处理的方法
  • 提高处理 Java 应用内存溢出问题的能力

主要参数

  • -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath
  • -XX:OnOutOfMemoryError
  • -XX:+ExitOnOutOfMemoryError
  • -XX:+CrashOnOutOfMemoryError

下面我们来详细的看下每个参数的意义和用法。

1. -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath

当 JVM 发生内存溢出时,-XX:+HeapDumpOnOutOfMemoryError 参数会触发 JVM 自动生成堆转储文件(Heap Dump),而 -XX:HeapDumpPath 参数则指定堆转储文件的存储路径。

什么是 Heap Dump?

Heap Dump 是 JVM 堆内存的快照,它包含了内存中所有对象的详细信息,包括对象的类型、大小、引用关系等。通过分析 Heap Dump,我们可以:

  • 定位内存泄漏的原因
  • 优化内存使用,减少内存浪费
  • 提升应用性能

如何使用?

在启动 JVM 时,添加以下参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof

当发生内存溢出时,JVM 会在指定路径生成一个 .hprof 文件,这就是 Heap Dump 文件。

如何分析 Heap Dump?

常用的 Heap Dump 分析工具有:

  • Eclipse Memory Analyzer (MAT):功能强大,支持泄漏检测、对象统计等功能。
  • HeapHero:可视化分析工具,支持多种内存分析技术。

2. -XX:OnOutOfMemoryError

-XX:OnOutOfMemoryError 参数允许我们在 JVM 发生内存溢出时,执行一个自定义的 shell 脚本或命令。

大多数时候,内存溢出并不会导致整个应用都Crash掉,但可能你需要在这里把应用重启一下。

为什么需要它?

内存溢出可能会导致应用处于不稳定状态,甚至崩溃。通过执行自定义脚本,我们可以:

  • 优雅地关闭应用
  • 重启应用
  • 发送告警通知

如何使用?

在启动 JVM 时,添加以下参数:

-XX:OnOutOfMemoryError="/path/to/script.sh"

当发生内存溢出时,JVM 会执行指定的脚本。

当给JVM传递上述参数的时候,如果发生了内存溢出,JVM会调用shell脚本,在这个脚本中你可以去用优雅的办法来重启你的应用。

3.-XX:+CrashOnOutOfMemoryError

-XX:+ExitOnOutOfMemoryError 参数会在 JVM 发生内存溢出时,强制退出 JVM。同时,JVM会产生文本和二进制格式的崩溃日志。

为什么需要它?

在某些场景下,我们希望应用在发生内存溢出时立即退出,而不是继续运行在不稳定的状态。

但是,我是不建议配置上这个参数的,我们应该是以一种优雅的方式退出程序,粗暴的退出方式可能会损害正在进行的事务。

注意事项

  • 该参数不会生成 Heap Dump 文件,也不会执行 -XX:OnOutOfMemoryError 指定的脚本。
  • 退出时不会执行 finally 块和关闭钩子,可能会导致资源未释放。

某个应用配置了-XX:+CrashOnOutOfMemoryError这个参数,当发生内存溢出的时候,JVM立马就退出了,并且在控制台有如下日志打印:

Aborting due to java.lang.OutOfMemoryError: GC overhead limit exceeded
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (debug.cpp:308), pid=26064, tid=0x0000000000004f4c
#  fatal error: OutOfMemory encountered: GC overhead limit exceeded
#
# JRE version: Java(TM) SE Runtime Environment (8.0_181-b13) (build 1.8.0_181-b13)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode windows-amd64 compressed oops)
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\workspace\tier1app-svn\trunk\buggyapp\hs_err_pid26064.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
日志中可以看出来,在C:\workspace\tier1app-svn\trunk\buggyapp\hs_err_pid26064.log目录下生成了崩溃日志文件,它里面包含了崩溃的详细信息。有些工具(fastThread)可以用来分析这个日志, 但是大多数时候,这些信息都很基础,根本无法定位内存溢出的原因。

4.-XX:+ExitOnOutOfMemoryError

如果传递了这个参数,当发生内存溢出的时候,JVM就会立马退出。如果你想在发生内存溢出的时候关闭应用那么可以使用这个参数。我一般也不会使用这个参数,道理同上。

我之前也遇到过配置了这个参数的应用,在发生内存溢出之后应用就退出了,不同的是这次JVM任何日志也没有产生,直接就退出了。

总结

JVM 提供了多个处理内存溢出的参数,我们可以根据实际需求进行选择和配置。建议在生产环境中:

  • 配置 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath,以便在内存溢出时生成 Heap Dump 文件,便于分析问题。
  • 配置 -XX:OnOutOfMemoryError,在内存溢出时执行自定义脚本,例如重启应用。
  • 不建议配置 -XX:+ExitOnOutOfMemoryError 和 -XX:+CrashOnOutOfMemoryError,除非有特殊需求。

通过合理配置这些参数,我们可以更有效地处理 Java 应用的内存溢出问题,提升应用的稳定性和可靠性。