捕获并分析 Java 堆转储
堆转储包含正在运行的 Java 应用程序在 Java 堆上使用的所有活动对象的快照。 本教程介绍堆转储、其各种格式及其重要性。
此外,我们将通过一个示例演示 OutOfMemoryError,这将导致捕获堆转储的各种方法以及分析它的工具。
堆转储及其格式简介
堆包含我们通过实例化类创建的所有对象。 Java 运行时的每个类也在这个堆中创建。
该堆是在 JVM(Java 虚拟机)启动时创建的,并且可以在运行时扩展/收缩以调整应用程序中销毁或创建的对象。
每当堆满时,垃圾收集过程就会运行,该过程收集所有不再使用的对象,或者我们可以说不再引用的对象(您可以在此处找到有关内存管理的更多信息。通常,堆转储以二进制格式 hprof 文件存储。
我们可以检索有关每个对象实例的详细信息,例如类型、类名、地址、大小以及实例是否包含对另一个对象的引用。 堆转储可以采用以下两种格式之一:
- 便携式堆转储格式(也称为 PHD 格式)
- 经典格式
请记住,可移植堆转储是默认格式,并且是二进制格式,必须对其进行处理以进行进一步分析。 另一方面,经典格式是人类可读的 ASCII 文本。
您可以在这里阅读有关这两种格式的信息。
使用堆转储的重要性
通常,当应用程序因 OutOfMemoryError 崩溃或 Java 应用程序消耗的内存超出预期时,我们会利用堆转储的优势。
堆转储帮助我们确定错误的主要原因和其他详细信息,例如每个类中的对象数量、每个类的内存使用情况等。
它还有助于捕获应用程序的每个 Java 对象占用的内存大小。 所有这些捕获的信息对于查找导致内存泄漏问题的实际代码都非常有用。
让我们看一个导致 OutOfMemoryError 的代码示例,这将导致捕获 Java 堆转储的各种方式。
Java 中导致 OutOfMemoryError 的示例代码
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<byte[]> myList = new ArrayList<>();
int index = 0;
while (true) {
// 1MB each iteration, 1 x 1024 x 1024 = 1048576
byte[] bytes = new byte[1048576];
myList.add(bytes);
Runtime runTime = Runtime.getRuntime();
System.out.printf("[%d] free memory is: %s%n",index++, runTime.freeMemory());
}
}
}
上面的代码将通过执行 while 循环来不断分配内存,直到达到 Java 虚拟机无法分配足够内存的特定点。
此时,我们将得到 java.lang.OutOfMemoryError: Java heap space
错误。
...
...
...
[1510] free memory is: 14687728
[1511] free memory is: 12590576
[1512] free memory is: 10493424
[1513] free memory is: 8396272
[1514] free memory is: 6299120
[1515] free memory is: 4201968
[1516] free memory is: 2104320
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Test.main(Test.java:16)
这就是我们需要进行heap dump分析来查找导致OutOfMemoryError的原因。 可以分两步完成。
首先捕获堆转储,然后分析堆转储文件以查找可疑原因。
捕获堆转储的不同方法
有多种方法可以捕获堆转储。 下面我们就来一一了解一下。
- jmap
- JVisualVM
- jcmd
- Generate Heap Dump Automatically
- JMX
使用jmap捕获堆转储
jmap 工具打印正在运行的 Java 虚拟机 (JVM) 中的内存统计信息; 我们还可以将它用于远程和本地进程。
我们使用 -dump 选项使用 jmap 工具捕获堆转储,我们可以从 JDK 主目录的 bin 文件夹中使用该工具。
句法:
jmap -dump:[live],format=b,file=<file-path> <pid>
示例:
C:\Program Files\Java\jdk-18\bin> jmap -dump:live,format=b,file=/temp/dump.hprof 12876
以下是我们上面指定的选项的简要说明:
参数 | 说明 |
---|---|
live | 该参数可选; 如果设置了,它将只打印具有活动引用的对象。 它忽略了那些准备好被垃圾收集的。 |
format=b | 用于指定转储文件的保存格式。 在这里,我们使用了 b,这意味着该转储文件将采用二进制格式。 如果不设置该参数,结果是一样的。 |
file | 这是转储将被写入的文件。 |
pid | 表示Java进程的id。 |
注意,我们可以使用jps命令来获取pid。 另外,jmap是作为JDK中的实验工具引入的,并且不受支持; 因此,在某些情况下,您可能必须使用其他工具而不是使用 jmap。
使用 JVisualVM 捕获堆转储
JVisualVM 是一个图形用户界面,使我们能够跟踪 Java 应用程序的分析和故障排除。 它简单易用,可以让我们捕获堆转储。
据此,它可在 Oracle JDK 6、7 和 8 中使用。从 JDK 9 或更高版本开始,JVisualVM 不再随 Oracle JDK 提供; 用户如果想使用,必须单独下载。
让我们从 VisualVM.github.io 下载它,解压 .zip 文件,在 bin 文件夹中找到 VisualVM.exe,双击它,在出现提示时点击“我接受”按钮,您将看到以下屏幕。
当前正在运行的所有 Java 进程都将在“本地”下列出。 我们可以通过选择所需的 Java 进程、右键单击它并选择“堆转储”选项来捕获堆转储。
它将打开一个新选项卡,显示所有必要的信息。
使用 jcmd 捕获堆转储
这个工具也可以在JDK主目录的bin文件夹中找到; 在我们的例子中,它是 C:\Program Files\Java\jdk-18\bin。 如果您没有在默认位置安装 Java,您的位置可能会有所不同。
jcmd 向 Java 虚拟机发送命令请求。 请记住,我们必须在运行 Java 进程的同一台机器上使用它。
它有多个命令,其中之一是 GC.heap-dump,我们将使用它通过指定进程 ID (pid) 和输出文件的路径来捕获堆转储。 请参阅下面的 jcmd 命令的语法。
句法:
jcmd <pid> GC.head_dump <file-path>
例子:
C:\Program Files\Java\jdk-18\bin> jcmd 12876 GC.head_dump /temp/dump.hprof
与 jmap 一样,它也会生成二进制格式的转储。
自动捕获堆转储
我们学到的所有方法都在特定时间手动捕获堆转储,但在某些情况下,我们必须在发生 java.lang.OutOfMemoryError 时立即生成堆转储。
在这里,自动生成堆转储将帮助我们调查错误。
考虑到这些场景,Java 使用了 HeapDumpOnOutOfMemoryError ,这是一个命令行选项,可以在应用程序抛出 java.lang.OutOfMemoryError 时生成堆转储。
java -XX:+HeapDumpOnOutOfMemoryError
默认情况下,上述命令会将转储存储在应用程序运行位置的 java_pid
句法:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path>
例子:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/temp/heapdump.bin
现在,只要我们的应用程序通过此选项耗尽内存,我们就可以在日志中找到创建的包含堆转储的文件,如下所示。
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
During heap to java_pid12876.hprof...
Exception in thread "main" Head dump file created [4745371 bytes in 0.028 secs]
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
我们可以看到上面的文本被写入 java_pid12876.hprof 文件,并且使用此选项运行我们的应用程序时没有任何开销。
最好对所有应用程序使用此选项,特别是在生产中,因为您永远不知道何时会发生 OutOfMemoryError。
请记住,我们可以在运行时使用 HotSpotDiagnostic MBean 使用此选项。 为此,使用 JConsole 并将 HeapDumpOnOutOfMemoryError VM 选项设置为 true。
使用 JMX 捕获堆转储
在此方法中,我们将使用 HotSpotDiagnostic MBean,它提供接受以下两个参数的 dumpHeap 方法:
参数 | 说明 |
---|---|
outputFile | 是dump的输出文件的路径; 该文件必须具有 .hprof 扩展名才能保存转储。 |
live | 如果我们将其设置为 true,它只会转储内存中的活动对象,正如我们在本教程中使用 jmap 时了解到的那样。 |
我们可以通过两种方式调用它来捕获堆转储,以编程方式调用它或使用 JMX 客户端(例如位于 JDK 主目录的 bin 文件夹中的 JConsole)。 我们将在这里使用 JMX,但您可以在此处了解如何以编程方式调用它。 |
通过 JMX 客户端 (JConsole) 使用 HotSpotDiagnostic MBean 是打开 JConsole、连接到正在运行的 Java 进程、导航到 MBeans 选项卡并在 com.sun.management 下查找 HotSpotDiagnostic 的最简单方法。
我们可以在“操作”下拉列表下找到 dumpHeap 方法,在其中我们可以将 outputFile 和 live 参数指定到 p0 和 p1 文本字段中以执行 dumpHeap 操作,如下所示。
现在,是时候分析 Java 堆转储了。
分析 Java 堆转储
在Java堆转储中,我们需要查找使用高内存的对象,对象图查找未释放内存的对象,以及可达和不可达的对象。
Eclipse 内存分析器 (MAT) 最适合分析我们之前生成的 Java 堆转储。 我们将启动内存分析器工具并打开堆转储文件来执行此操作。
在 Eclipse Memory Analyzer (MAT) 中,我们将有两种对象大小,下面对此进行简要说明。
- 浅堆大小 - 对象的浅堆是其在内存中的大小。
- 保留堆大小 - 对象被垃圾回收后将释放的内存量。
打开堆转储文件后,我们可以看到应用程序内存使用情况的摘要。 现在,我们可以轻松找出导致 OutOfMemoryError 的原因。
相关文章
如何在 Java 中延迟几秒钟的时间
发布时间:2023/12/17 浏览次数:217 分类:Java
-
本篇文章主要介绍如何在 Java 中制造程序延迟。本教程介绍了如何在 Java 中制造程序延时,并列举了一些示例代码来了解它。
如何在 Java 中把 Hashmap 转换为 JSON 对象
发布时间:2023/12/17 浏览次数:187 分类:Java
-
它描述了允许我们将哈希图转换为简单的 JSON 对象的方法。本文介绍了在 Java 中把 Hashmap 转换为 JSON 对象的方法。我们将看到关于创建一个 hashmap,然后将其转换为 JSON 对象的详细例子。
如何在 Java 中按值排序 Map
发布时间:2023/12/17 浏览次数:171 分类:Java
-
本文介绍了如何在 Java 中按值对 Map 进行排序。本教程介绍了如何在 Java 中按值对 Map
进行排序,并列出了一些示例代码来理解它。
如何在 Java 中打印 HashMap
发布时间:2023/12/17 浏览次数:192 分类:Java
-
本帖介绍了如何在 Java 中打印 HashMap。本教程介绍了如何在 Java 中打印 HashMap 元素,还列举了一些示例代码来理解这个主题。
在 Java 中更新 Hashmap 的值
发布时间:2023/12/17 浏览次数:146 分类:Java
-
本文介绍了如何在 Java 中更新 HashMap 中的一个值。本文介绍了如何在 Java 中使用 HashMap 类中包含的两个方法-put() 和 replace() 更新 HashMap 中的值。
Java 中的 hashmap 和 map 之间的区别
发布时间:2023/12/17 浏览次数:79 分类:Java
-
本文介绍了 Java 中的 hashmap 和 map 接口之间的区别。本教程介绍了 Java 中 Map 和 HashMap 之间的主要区别。在 Java 中,Map 是用于以键值对存储数据的接口,
在 Java 中获取用户主目录
发布时间:2023/12/17 浏览次数:218 分类:Java
-
这篇文章向你展示了如何在 Java 中获取用户主目录。本教程介绍了如何在 Java 中获取用户主目录,并列出了一些示例代码以指导你完成该主题。
Java 中 size 和 length 的区别
发布时间:2023/12/17 浏览次数:179 分类:Java
-
这篇文章教你如何知道 Java 中大小和长度之间的区别。本教程介绍了 Java 中大小和长度之间的区别。我们还列出了一些示例代码以帮助你理解该主题。
Java 中的互斥锁
发布时间:2023/12/17 浏览次数:111 分类:Java
-
了解有关 Java 中互斥锁的一切,在计算机科学领域,互斥或互斥被称为并发控制的属性。每台计算机都使用称为线程的最小程序指令序列。有一次,计算机在一个线程上工作。为了更好地理解,