分析 Java 堆转储文件
本文旨在帮助您使用 Eclipse MAT 分析获取的堆转储文件。它涵盖了如何解析大型堆文件以及需要寻找的内容。
当您遇到 OutOfMemory 异常时,如果在 neo4j.conf 文件中进行以下设置,将生成一个 .hprof 文件
dbms.jvm.additional=-XX:+HeapDumpOnOutOfMemoryError
您还可以修改以下设置来指定目录路径,但请确保在此类错误发生时有足够的磁盘空间。 |
dbms.jvm.additional=-XX:HeapDumpPath=/var/tmp/dumps
dbms.jvm.additional=-XX:OnOutOfMemoryError="tar cvzf /var/tmp/dump.tar.gz /var/tmp/dump;split -b 1G /var/tmp/dump.tar.gz;"
此文件是系统上运行的 Java 进程的堆部分的镜像。文件结构取决于您运行 Neo4j 所使用的 JVM 供应商。
Oracle JDK、Open JDK 将生成 hprof 文件,可以使用大多数可用工具进行分析。对于 IBM 堆转储文件,您需要使用 IBM heap analyzer 或其他专有工具进行解析。
更改 MemoryAnalyzer.ini 中的设置
在您的本地环境中
您需要为该进程分配与堆转储文件大小相同的内存。
例如:如果堆大小约为 15GB,则分配 17GB。
对于大型堆转储文件(> 25GB),请参见下一节。
编辑 MemoryAnalyzer.ini(在 macOS 上,它位于 /Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini)
添加或更改设置
-Xms10G -Xmx25G
在远程机器上
最好将其上传到 AWS/GCP 等云平台上具有大量磁盘和 RAM 的实例。如果选择 AWS,请使用竞价实例。
然后需要挂载 EBS 存储,创建一个 250GB 的卷,并将其挂载到 EC2 实例。格式化该卷并将其挂载到您的 Amazon Linux 实例上。
记下 instanceid 和 storageid,确保使用后资源已被正确释放。
如果堆大小约为 61GB,则解析需要两倍的磁盘空间。如下所示
$ du -ch java_pid19820*
116M java_pid19820.a2s.index
5.6G java_pid19820.domIn.index
17G java_pid19820.domOut.index
61G java_pid19820.hprof #original heap dump
256K java_pid19820.i2sv2.index
11G java_pid19820.idx.index
29G java_pid19820.inbound.index
197M java_pid19820.index
4.5G java_pid19820.o2c.index
12G java_pid19820.o2hprof.index
11G java_pid19820.o2ret.index
29G java_pid19820.outbound.index
988K java_pid19820.threads
68K java_pid19820_Component_Report_sel.zip
180G total
-
先决条件:安装 Java 并确保有 250GB 可用空间
-
下载适用于 Linux 的 MemoryAnalyzer 工具:下载
-
将其解压缩到目录中
-
编辑 MemoryAnalyzer.ini 以调整 -Xms 和 -Xmx 内存设置
-startup plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar --launcher.library plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.700.v20180518-1200 -vmargs -Xms30G -Xmx100G
在远程机器上解析文件
如果您在本地机器上运行 Eclipse MAT 并且有足够的资源,此步骤是可选的。如果索引文件丢失,在打开堆转储文件时会自动创建。
将本地目录与远程目录同步
为了加快速度,您可以使用 rsync over ssh。优点是如果发生崩溃可以恢复,并且 -z 标志可以启用压缩。
示例
# on the remote machine
$ mkdir ${REMOTE_DIR}/parsed_files
$ mv *.index ${REMOTE_DIR}/parsed_files/
# on your local machine
$ rsync -P -e "ssh -i ${PATH_TO_KEY}" ec2-user@${REMOTE_IP}:${REMOTE_DIR}/heapdump.zip .
$ rsync -Prz -e "ssh -i ${PATH_TO_KEY} ec2-user@${REMOTE_IP}:${REMOTE_DIR}/parsed_files/ .
打开 Eclipse MAT
要打开堆转储文件,请转到 File > Open Heap Dump(不是 Acquire Heap Dump),然后浏览到您的堆转储文件位置。
无需打开现有报告,如果弹出对话框,请按取消。
在 Overview 标签页中,左键单击最大的对象
选择 "list objects" > "with outgoing references"。

它将打开一个新标签页,其中包含所有元素的列表。

展开第一级,然后展开第二级的所有内容。
Cypher 查询字符串
堆转储文件中有很多对象,无需查看 Object[]、byte[]、Strings 等。
您可能需要过滤包含 PreParsed 的类。找到后,列出它们的出站引用,以交叉检查哪个实例最多。将打开一个新的标签页,您将能够看到 Cypher 查询的原始语句 (rawStatement)。

检查线程转储文件
结合在生成堆转储之前获取的线程转储文件
垃圾收集器无法收集线程对象,直到线程系统也取消引用该对象,而如果线程处于活动状态,这种情况就不会发生。
因此,如果堆中有大量内存,则应该有一个潜在的长时间运行的线程与您的大对象相关联。
要找到它,请在线程转储文件中查找线程名称。
$ grep neo4j.BoltWorker-394 *
5913-tdump-201903291746.log:"neo4j.BoltWorker-394 [bolt]" #620 daemon prio=5 os_prio=0 tid=0x00007fb737619800 nid=0x8cec waiting on condition [0x00007fb38d00f000]
5913-tdump-201903291751.log:"neo4j.BoltWorker-394 [bolt] [/www.xxx.yyy.zzz:57570] " #620 daemon prio=5 os_prio=0 tid=0x00007fb737619800 nid=0x8cec runnable [0x00007fb38d00b000]
5913-tdump-201903291756.log:"neo4j.BoltWorker-394 [bolt] [/www.xxx.yyy.zzz:57570] " #620 daemon prio=5 os_prio=0 tid=0x00007fb737619800 nid=0x8cec runnable [0x00007fb38d00b000]
请注意,线程转储文件包含在堆转储文件中。它们以纯文本形式存在于文件中,但在 Eclipse Mat 中您没有 STATE 信息。您可以使用 VisualVM 等其他工具获取它们。
$ head -10 java_pid19820.threads
Thread 0x7fd64b0e1610
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.addConditionWaiter()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; (AbstractQueuedSynchronizer.java:1855)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(J)J (AbstractQueuedSynchronizer.java:2068)
at java.util.concurrent.LinkedBlockingQueue.poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object; (LinkedBlockingQueue.java:467)
at com.hazelcast.util.executor.CachedExecutorServiceDelegate$Worker.run()V (CachedExecutorServiceDelegate.java:210)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V (ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run()V (ThreadPoolExecutor.java:624)
at java.lang.Thread.run()V (Thread.java:748)
at com.hazelcast.util.executor.HazelcastManagedThread.executeRun()V (HazelcastManagedThread.java:76)
at com.hazelcast.util.executor.HazelcastManagedThread.run()V (HazelcastManagedThread.java:92)
此页面有帮助吗?