V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
manecocomph
V2EX  ›  Java

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

  •  
  •   manecocomph · 2021-02-05 13:29:47 +08:00 · 2075 次点击
    这是一个创建于 1387 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    原文链接: 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).

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2803 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:09 · PVG 08:09 · LAX 16:09 · JFK 19:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.