Java 无法 attach 到目标进程, 使用 core dump 转换为 Java heap dump
有时候, 当我们尝试对一个已经 GC overhead 非常高的 Java 应用进程去做 heap dump 的时候, 或者使用 jstack/jcmd 去做 thread dump 的时候, 发现我们跟本没办法 attach 到目标 Java 进程, 得到下面的错误:
/usr/bin/jcmd 7674 GC.heap_dump /tmp/heap.log.hprof
7674:
com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106)
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63)
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:213)
at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:140)
at sun.tools.jcmd.JCmd.main(JCmd.java:129)一般我们会多尝试几次, 期望有哪次可以 attach 到目标 Java 进程, 不过有时候, 尝试好多次都无法达到目的.
除了这种尝试的方法之外, 我们还可以尝试另外一种方式: 先获得目标进程的 core dump, 再从 core dump 转成 Java heap dump.
具体的方法步骤是:
安装 gcore
$ sudo apt install gdb -y #gcore command in gdb package设置 core dump 可允许的大小. 通常生产环境的 core dump 都是禁止的, 一方面 core dump 特别大, 占用磁盘比较大, 另外 core dump 可能包含运行时内存的敏感数据. 如果你的生产环境的 core size 是 unlimited 的或者特别大, 可以忽略这步.
$ sudo cp /etc/security/limits.conf /etc/security/limits.d/ # 复制limits.conf模板文件到配置文件夹 $ sudo vim /etc/security/limits.d/limits.conf # 编辑配置文件, 修改我们目标 Java 进程的用户的配置 #### 我们假设我们的目标进程使用 appUser 运行, 给他设置 core file 的 hard, soft 允许大小 appUser hard core unlimited appUser soft core unlimited $ sudo su appUser # 切换到 appUser $ ulimit -a # 查看它的 core file size 的 hard, soft 大小 ### 有时候, 即便你改好了, 还是看到 core size 是0, 不过不要悲观, 继续下去, 可以参数 core dump. $ exit # 退出 appUser获取 core dump
$ pgrep java # 显示查看目标进程的 pid $ sudo gcore <pid> # 使用 gcore 获取 core dump, 根据目标进程的内存占用大小, 可能会花费不同的时间 ### 有时候使用别的用户, 即使使用 sudo 也不能 ptrace, 会得到 ptrace 无法 attach 的错误, 这时候, 切换到 ### appUser 用户, 直接 gcore <pid> 就行. 如果报无法写入, 看你在哪个文件夹, 到一个可写的文件夹就好了 $ ls -lah core.<pid> # 查看获取的 core dump 的大小, 通常在当前目录把 core file 的 hard, soft 修改的配置改回来
$ sudo rm -f /etc/security/limits.d/limits.conf # 之前我们把模板加到这个文件, 现在删掉转换 core dump 到 Java heap dump. 一定要使用运行对应目标进程运行的 JDK.
如果你是 Hotspot JDK (Oracle 或者 OpenJDK):$ find /app -name jmap # 找到运行目标进程的 JDK 里面的 jmap 命令 $ sudo /usr/bin/jmap -dump:format=b,file=heap.hprof /usr/bin/java ./core.<pid> #转换如果你是 IBM J9 JDK:
$ find /app -name jextract # 找到运行目标进程的 JDK 里面的 jextract 命令 $ jextract core.<pid> # 从 core dump 转成 heap dump core.<pid>.zip ## 对于这个 core.<pid>.zip 使用 MAT 并且安装的 dtfj 插件, 可以打开, 或者使用 IBM JDK 的 jdmpview 工具里 ## 面的 heapdump 命令, 可以把 这个 zip 文件转换成 IBM PHD 格式的 heap dump, 然后使用带 DTFJ 插件的 MAT ## 工具分析. $ /usr/bin/jdmpview $ heapdump $ ls -lah -rw-r--r-- 1 root root 7.5G Aug 21 17:51 core.16751 -rw-rw-r-- 1 user1 user1 926M Aug 21 19:09 core.16751.zip -rw-rw-r-- 1 user1 user1 19M Aug 21 19:46 core.16751.zip.phd到这里, 获取 heap dump 的过程结束, 下面是怎么从 prod 环境复制 heap dump 到本地环境. 你可以选择使用 scp 或者 rsync 复制到本地, 也可以向下面一样, 使用 nc 建立一个下载服务器, 从本地下载.
设置 nc 下载服务
$ sudo apt install netcat -y # 若 nc 没有安装, 则安装 $ sudo nc -4 -v -l 7070 < ./heap.hprof # 一种 nc 支持这种参数 $ sudo nc -v -l -p 7070 < ./heap.hprof # 另外一种支持这种参数下载到本地
- 使用浏览器访问 http://
:7070/ - 或者使用 curl 命令去下载 → curl "http://
:7070/" --output heap.hprof
- 使用浏览器访问 http://
清理
$ rm core.<pid> dump.hprof有了 Heap dump, 你就可以使用 MAT 或者 JVisualVM 查看里面的内容了.