跳过正文
为什么应该避免在生产环境中启用 HeapDumpOnOutOfMemoryError
  1. 文章/

为什么应该避免在生产环境中启用 HeapDumpOnOutOfMemoryError

·1536 字·4 分钟
NeatGuyCoding
作者
NeatGuyCoding

1. 为什么我们不推荐启用 HeapDumpOnOutOfMemoryError
#

1.1. 启用 HeapDumpOnOutOfMemoryError 后,哪些 OutOfMemoryError 实际会触发它?
#

这里有个有趣的事情 - 一旦你启用了 HeapDumpOnOutOfMemoryError,并不是每个 OutOfMemoryError 都会实际触发堆转储!让我们分解不同类型的 OutOfMemoryError 异常,看看哪些会配合:

  1. OutOfMemoryError: Java heap spaceOutOfMemoryError: GC overhead limit exceeded:这两个都表示 Java 堆内存不足 - 一个在分配时剩余空间不足时发生,另一个达到特定阈值。这两个都会触发 HeapDumpOnOutOfMemoryError

  2. OutOfMemoryError: unable to create native thread:当系统无法创建新的平台线程时发生。这个不会触发 HeapDumpOnOutOfMemoryError

  3. OutOfMemoryError: Requested array size exceeds VM limit:当请求的数组大小超过堆内存限制时抛出。这会触发 HeapDumpOnOutOfMemoryError

  4. OutOfMemoryError: Compressed class spaceOutOfMemoryError: Metaspace:两者都与元空间问题相关。两者都会触发 HeapDumpOnOutOfMemoryError

  5. OutOfMemoryError: Cannot reserve xxx bytes of direct buffer memory (allocated: xxx, limit: xxx):在 DirectByteBuffer 中,系统首先从 Bits 类请求配额,该类维护一个全局 totalCapacity 变量跟踪所有 DirectByteBuffer 大小。你可以使用 -XX:MaxDirectMemorySize 限制这个。这不会触发 HeapDumpOnOutOfMemoryError

  6. OutOfMemoryError: map failed:在文件内存映射(MMAP)期间系统内存不足时发生。这不会触发 HeapDumpOnOutOfMemoryError

还有一些额外情况:

  1. Shenandoah 分配区域位图内存问题触发 OutOfMemoryError 会触发 HeapDumpOnOutOfMemoryError

  2. OutOfMemoryError: Native heap allocation failed:消息可能因操作系统而异,但通常包括"native heap"。这通常与 Java 对象堆无关,而是其他内存分配失败。这些不会触发 HeapDumpOnOutOfMemoryError

1.2. 为什么我们建议不要启用 HeapDumpOnOutOfMemoryError
#

让我们深入了解 HeapDumpOnOutOfMemoryError 实际如何工作:

  1. JVM 进入 safepoint,暂停所有应用线程。对于 HeapDumpOnOutOfMemoryError,它使用单线程转储(与可以使用多线程的 jcmd/jmap 不同)创建多个文件。然后退出 safepoint。

  2. 然后将这些多个文件合并为一个并压缩。

这里的主要瓶颈是第一步 - 写入过程 - 具体来说,是磁盘 I/O 性能。让我们看看一些真实的云存储性能标准:

  1. AWS EFS(标准存储):https://docs.aws.amazon.com/efs/latest/ug/performance.html
  2. AWS EBS(SSD 等效):https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html

对于 4GB 堆,使用 EFS(对应不到 100GB 磁盘),写入至少需要 4 * 1024 / 300 = 13.65 秒(这还是峰值性能!)。如果峰值性能已经在其他地方使用,你需要 4 * 1024 / 15 = 273 秒。即使使用 EBS,你仍然需要 4 * 1024 / 1000 = 4 秒。记住,这是你的应用线程在 stop-the-world 状态下完全冻结的时间!这甚至没有考虑同一台机器上的多个容器实例。从成本角度来看,我们不能给每个微服务 AWS EBS(SSD 等效)存储。

所以我们的建议?完全跳过 HeapDumpOnOutOfMemoryError

2. 用什么替代 HeapDumpOnOutOfMemoryError?
#

2.1. 使用 JFR 进行内存泄漏检测
#

当我需要追踪 OutOfMemoryError 问题时,我通常依赖 JFR 的对象分配样本和旧对象样本数据来定位有问题的对象。只有当这些方法没有产生结果时,我才会考虑生成堆转储。

2.2. 为什么遇到 OutOfMemoryError 的微服务应该重启?
#

事情是这样的 - 大多数代码,包括 JDK 源代码,不会在每个内存分配点考虑 OutOfMemoryError。这可能导致应用状态不一致。例如,在 HashMap 重新哈希操作期间,如果中途抛出 OutOfMemoryError,之前更新的状态就会损坏。大多数库很少捕获 Throwable - 它们通常只捕获 Exception。

在每个内存分配点处理 OutOfMemoryError 根本不现实。为了防止 OutOfMemoryError 导致的意外一致性问题,最安全的方法是使服务离线并重启它。

2.3. 如何实现遇到 OutOfMemoryError 的微服务的自动重启?
#

你可以使用 -XX:OnOutOfMemoryError="/path/to/script.sh" 来指定一个处理以下内容的脚本:

  1. 优雅的微服务关闭
  2. 微服务重启

对于 Spring Boot 应用,考虑启用对 /actuator/shutdown 的本地访问以优雅关闭微服务(尽管一些社区成员报告这在发生 OutOfMemoryError 时可能会挂起 - 这可能是由于启用了 HeapDumpOnOutOfMemoryError,如第 1.2 节所述)。Kubernetes 会自动启动一个新实例。

相关文章

全网最硬核 JDK 分析 - 4. OpenJDK JVM 内存结构实现

·69418 字·139 分钟
一次全面深入的 JVM 内存架构剖析,涵盖堆内存、元空间、线程栈以及压缩对象指针。本文从内存分配流程、Native Memory Tracking 出发,并通过 jol、jhsdb、JFR 等工具示例,帮助理解 JVM 内存管理内部机制。

全网最硬核 JDK 分析 - 3. Java 新内存模型解析与实验

·25486 字·51 分钟
从规范到实现深入探讨 Java 内存模型(JMM),涵盖内存屏障、CPU 重排序和 Java 9+ VarHandle API。了解一致性、因果性、共识性,以及 volatile、final 和其他同步机制在底层的工作原理,并提供实用的 jcstress 示例。