原文格式稍好 , 欢迎评论讨论, 讨论能学到更多知识.
原文链接: 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 提供了 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 是操作系统进程的瞬时的 snapshot. 我们可以先做一个 core dump, 然后通过 core dump 转 heap dump 的方式, 获得我们期望的 heap dump. 详细步骤如下:
首先 安装 gcore $ sudo apt install gdb -y #gcore command in gdb package
把 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
获取 core dump
$ pgrep java # 显示查看目标进程的 pid
$ sudo 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.
$ find /app -name jmap # 找到运行目标进程的 JDK 里面的 jmap 命令 $ sudo /usr/bin/jmap -dump:format=b,file=heap.hprof /usr/bin/java ./core.<pid> #转换
等使用完之后, 清理 dump 文件(因为 dump 文件比较占磁盘)
$ rm core.<pid> dump.hprof
至此, 我们终于有了这个 heap dump 宝藏, 剩下的就是去宝藏里面查找我们需要的信息了(后续将会介绍如何使用 mat 有效的分析 heap dump).