分析 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 堆分析器或其他专有工具对其进行解析。
更改 MemoryAnalyzer.ini 中的设置
在您的本地环境中
您需要为进程分配与您拥有的堆转储文件大小一样多的内存。
例如:如果堆大约为 15GB,则分配 17GB。
对于大型堆转储(> 25G),请参见下一节。
编辑 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 通过 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
要打开堆转储,请转到文件 > 打开堆转储(不是获取堆转储)并浏览到您的堆转储位置。
无需打开现有报告,如果您有模式对话框,请按取消。
在“概述”选项卡中,左键单击最大的对象。
选择“列出对象”>“具有传出引用”。

它将打开一个包含所有元素列表的新选项卡。

展开第一级,然后展开第二级的全部内容。
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)
此页面是否有帮助?