java.lang.OutOfMemoryError 有 9 种类型,每种类型都表示 Java 应用程序中与内存相关的独特问题。其中,它是开发人员遇到的最普遍和最具挑战性的错误之一。在这篇文章中,我们将深入探讨此错误背后的根本原因,探索潜在的解决方案,并讨论解决此问题的有效诊断方法。让我们用知识和工具武装自己,以征服这个共同的对手。java.lang.OutOfMemoryError: Java heap space

如何解决Java 堆空间出现: OutOfMemoryError:异常_java

JVM 内存区域

为了更好地理解 ,我们首先需要了解不同的 JVM 内存区域(请参阅此视频剪辑,它很好地介绍了不同的 JVM 内存区域)。但简而言之,JVM 具有以下内存区域:OutOfMemoryError



如何解决Java 堆空间出现: OutOfMemoryError:异常_JVM_02

图 1:JVM 内存区域

  1. 年轻一代: 新创建的应用程序对象存储在此区域中。
  2. 老一代: 生存期较长的应用程序对象将从新生代提升到老一代。基本上,此区域包含生存期较长的对象。
  3. 元空间: 执行程序所需的类定义、方法定义和其他元数据存储在 Metaspace 区域中。此区域是在 Java 8 中添加的。在此之前,元数据定义存储在 PermGen 中。从 Java 8 开始,PermGen 被 Metaspace 取代。
  4. 线程: 每个应用程序线程都需要一个线程堆栈。为线程堆栈分配的空间(包含方法调用信息和局部变量)存储在此区域中。
  5. 代码缓存: 存储方法的已编译本机代码(机器代码)以实现高效执行的内存区域存储在此区域中。
  6. 直接缓冲器: 现代框架(即 Spring WebClient)使用 ByteBuffer 对象进行高效的 I/O 操作。它们存储在此区域中。
  7. GC (垃圾回收): 自动垃圾回收工作所需的内存存储在此区域中。
  8. JNI(Java 本机接口): 用于与本机库交互的内存和以其他语言编写的代码存储在此区域中。
  9. 杂项: 有一些区域特定于某些 JVM 实现或配置,例如内部 JVM 结构或保留内存空间,它们被归类为 “misc” 区域。

什么是“java.lang.OutOfMemoryError:Java 堆空间”?

如何解决Java 堆空间出现: OutOfMemoryError:异常_JVM_03

图 2:“java.lang.OutOfMemoryError:Java 堆空间”

当在“堆”(即 young 和 Ood)区域中创建的对象多于分配的内存限制(即 )时,JVM 将抛出 .-Xmxjava.lang.OutOfMemoryError: Java heap space

什么原因导致“java.lang.OutOfMemoryError:Java 堆空间”?

java.lang.OutOfMemoryError: Java heap space在以下情况下由 JVM 触发:

1. 流量增加

当流量出现 Spired 时,内存中会创建更多的对象。当创建的对象数超过分配的内存限制时,JVM 将引发“OutOfMemoryError: Java heap space”。

2. 代码错误导致内存泄漏

由于代码中的 bug,应用程序可能会无意中保留对不再需要的对象的引用。它可能导致内存中未使用的对象堆积,最终耗尽可用的堆空间,从而导致 .OutOfMemoryError

“OutOfMemoryError: Java heap space”的解决方案

以下是修复此错误的潜在解决方案:

1. 修复内存泄漏

使用本文中给出的方法分析内存泄漏或低效的内存使用模式。确保在不再需要对象时正确取消引用对象,以允许对它们进行垃圾回收。

2. 增加堆大小

如果由于流量增加而出现,则增加 JVM 堆大小 () 以向 JVM 分配更多内存。但是,请注意不要分配太多内存,因为这可能会导致更长的垃圾回收暂停时间和潜在的性能问题。OutOfMemoryError-Xmx

生成“OutOfMemoryError: Java heap space”的示例程序

为了更好地理解 ,让我们尝试模拟它。让我们利用 BuggyApp,一个简单的开源混沌工程项目。BuggyApp 可以生成各种性能问题,例如内存泄漏、线程泄漏、死锁和多线程。下面是 BuggyApp 项目中的程序,执行时可以进行模拟。java.lang.OutOfMemoryError: Java heap spaceBLOCKEDjava.lang.OutOfMemoryError: Java heap space

public class MapManager {              private static HashMap<Object, Object> myMap = new HashMap<>();              public void grow() {                   long counter = 0;             while (true) {                      if (counter % 1000 == 0) {                                  System.out.println("Inserted 1000 Records to map!");               }                        myMap.put("key" + counter, "Large stringgggggggggggggggggggggggg" + counter);                         ++counter;          }      } }

上面的程序有一个类,该类在内部包含一个分配给变量的对象。在该方法中,有一个无限循环不断填充对象。在每次迭代中,都会向 .由于它是一个无限循环,因此 object 将不断填充,直到堆容量饱和。一旦超过堆容量限制,应用程序将导致 。MapManagerHashMapmyMapgrow()while (true)HashMapkey-0Large stringgggggggggggggggggggggggg-0HashMapmyMapjava.lang.OutOfMemoryError: Java heap space

如何排查“OutOfMemoryError: Java heap space” 问题

这是一个两步过程:

1. 捕获堆转储

您需要在 JVM throws 之前从应用程序捕获堆转储。在这篇文章中,讨论了捕获堆转储的 8 个选项。您可以选择适合您需求的选项。我最喜欢的选项是在启动时将 JVM 参数传递给您的应用程序。OutOfMemoryError-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<FILE_PATH_LOCATION>

例:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tmp/heapdump.bin

当你传递上述参数时,JVM 将生成一个堆转储,并在抛出时将其写入 /opt/tmp/heapdump.binOutOfMemoryError

1
What is Heap Dump? 
2


3
Heap Dump is a snapshot of your application memory. It contains detailed information 
4
about the objects and data structures present in the memory. It will tell what 
5
objects are present in the memory, whom they are referencing, who are referencing, 
6
what is the actual customer data stored in them, what size of they occupy, are 
7
they eligible for garbage collection… They provide valuable insights into the memory 
8
usage patterns of an application, helping developers identify and resolve memory-related
9
issues.

2. 分析堆转储

捕获堆转储后,您需要使用 HeapHero、JHat 等工具来分析转储。

如何分析堆转储?

在本节中,让我们讨论如何使用 HeapHero 工具分析堆转储。

HeapHero 有两种模式:

  1. 云:您可以将转储上传到 HeapHero 云并查看结果。
  2. 本地:您可以注册以在本地计算机上安装 HeapHero,然后进行分析。

注意: 我更喜欢使用该工具的本地安装,而不是使用云版本,因为堆转储往往包含敏感信息(如 SSN、信用卡号、增值税等),并且不希望在外部位置分析转储。

安装该工具后,将堆转储上传到 HeapHero 工具。该工具将分析转储并生成报告。让我们回顾一下该工具为上述程序生成的报告。

如何解决Java 堆空间出现: OutOfMemoryError:异常_java_04

图 3:HeapHero 报告检测到内存问题

从上面的屏幕截图中,您可以看到 HeapHero 报告在应用程序中检测到问题,并指出该类占用了 99.96% 的总内存。该工具还提供内存的整体概览 (Heap Size、Class count、Object Count 等)。MapManager

如何解决Java 堆空间出现: OutOfMemoryError:异常_java_05

图 4:应用程序中最大的对象

以上是 HeapHero 的堆转储分析报告中的 “Largest Objects” 部分屏幕截图。本部分显示应用程序中存在的最大对象。在大多数情况下,应用程序中前 2-3 个最大的对象是导致应用程序中的内存泄漏的原因。让我们看看 “Largest Objects” 部分中提供了哪些重要信息。



如何解决Java 堆空间出现: OutOfMemoryError:异常_开发语言_06

图 5:最大对象细分

如果您注意到 #4 和 #5,您可以看到内存中存在实际数据。有了这些信息,您就可以知道应用程序中最大的对象以及应用程序中存在的值。如果您想查看谁创建或保留了最大对象的引用,您可以使用该工具的 “Incoming References” 功能。选择此选项后,它将显示引用该类的所有对象。MapManager

如何解决Java 堆空间出现: OutOfMemoryError:异常_应用程序_07

图 6:所选对象的传入引用

从此报告中,您可以注意到 MapManager 被 Object2 引用,而 Object2 又被 Object1 引用,而 Object1 又被 MemoryLeakDemo 引用。

我们介绍了一系列主题,从了解 JVM 内存区域到诊断和解决 .我们希望您发现这些信息有用且有见地。但我们的对话并没有就此结束。您的经验和见解对我们和您的读者同行来说都是无价的。我们鼓励您在下面的评论中分享您的遭遇。无论是您发现的独特解决方案、您信誓旦旦的最佳实践,甚至只是个人轶事,您的贡献都可以丰富每个人的学习体验。java.lang.OutOfMemoryError: Java heap spacejava.lang.OutOfMemoryError: Java heap space