Java 进程 hang 住了, 怎么拿到 heap dump?

2021-02-05 13:29:47 +08:00
 manecocomph

原文格式稍好 , 欢迎评论讨论, 讨论能学到更多知识.

原文链接: https://mp.weixin.qq.com/s/7SHlfonUEW8oWx_IAjuYRw

经常诊断 Java 问题, 做 heap dump 是常规项目, 因为 heap dump 就像一个宝藏一样, 里面是运行时的各种真实状态. 一般使用 JDK 提供的命令行工具或者使用封装好的特定编程接口, 很容易做出一个 heap dump. 但是如果遇到 JVM 已经不响应了, 如何才能做出一个 heap dump 呢?

场景重现

看到线上有个应用 CPU 使用率达到 100%, 不响应任何请求, 进一步检查发现是 GC 导致的, GC overhead 已经达到 100%. 这个时候, 为了发现内存堆积的真正原因, 需要做一个 heap dump.

首先尝试使用应用框架提供的 Restful API 的形式去做 heap dump, 可是等了 10 秒后还没响应. 这是因为: 内存已经不够用, 但是之前进来的请求还没完成, 各个线程都在等待内存给自己用, 可是内存却不能释放出来. 形成一个矛盾体: 应用线程等空闲内存继续运行, 却没有内存, 只能暂停, GC 线程拼命做 Full GC, 可是没有太多内存可以释放, 只能一遍一遍做.

Hotspot JVM 对于这种情况的默认处理是: 如果连续 5 次 Full GC, 每次 GC overhead 都超过 98%, 回收的空闲内存不足 2%, 就抛出 OOM. 不满足任何一个条件, 比如有次回收多于 2%, 就不会抛出 OOM.

这个时候, 由于所有应用线程都完成不了现有的任务, 所以无法承接新的请求, 导致不响应任何新请求.

JDK 提供的命令行 jcmd | jmap

早期 JDK 提供了 jmap 命令行工具, 后续的 JDK 又提供了 jcmd 命令行工具, 都可以帮助我们方便的去做 heap dump. 于是, 我们转而使用 jcmd 去尝试获得 heap dump.

然而, 这次又失败了: 无法 attach 到目标进程:

/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)

偶尔情况下, 如果我们多试几次, 有可能某次正好碰到 GC 线程释放出部分内存, 让 JVM 可以响应我们的请求. 这种概率非常小.

那么有没有其它比较靠谱的办法呢?

Core dump 转 heap dump

core dump 是操作系统进程的瞬时的 snapshot. 我们可以先做一个 core dump, 然后通过 core dump 转 heap dump 的方式, 获得我们期望的 heap dump. 详细步骤如下:

  1. 首先 安装 gcore $ sudo apt install gdb -y #gcore command in gdb package

  2. 把 core size limit 改为 unlimited $ sudo cp /etc/security/limits.conf /etc/security/limits.d/ # 复制 limits.conf 模板文件到配置文件夹 $ sudo vim /etc/security/limits.d/limits.conf # 编辑配置文件, 修改我们目标 Java 进程的用户的配置 appUser hard core unlimited appUser soft core unlimited

  3. 获取 core dump

    $ pgrep java # 显示查看目标进程的 pid $ sudo gcore <pid>
    $ ls -lah core.<pid> # 查看获取的 core dump 的大小, 通常在当前目录

  4. 把 core file 的 hard, soft 修改的配置改回来 $ sudo rm -f /etc/security/limits.d/limits.conf # 之前我们把模板加到这个文件, 现在删掉

  5. 转换 core dump 到 Java heap dump. 一定要使用运行对应目标进程运行的 JDK.

    $ find /app -name jmap # 找到运行目标进程的 JDK 里面的 jmap 命令 $ sudo /usr/bin/jmap -dump:format=b,file=heap.hprof /usr/bin/java ./core.<pid> #转换

  6. 等使用完之后, 清理 dump 文件(因为 dump 文件比较占磁盘)

    $ rm core.<pid> dump.hprof

至此, 我们终于有了这个 heap dump 宝藏, 剩下的就是去宝藏里面查找我们需要的信息了(后续将会介绍如何使用 mat 有效的分析 heap dump).

2103 次点击
所在节点    Java
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/751547

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX