1

原作者: Jarek Przygódzki (http://twitter.com/jarekprzyg... (http://github.com/jarek-przyg...

用 JDK 生成 JVM 堆内存转储非常简单,因为几乎每个Java开发人员都知道 JDK 附带的jmapjcmd工具。但是 JRE 呢?

有人认为您需要JDK或至少其中的一部分,但这不是事实。答案就在jattach中,该工具通过 JVM 黑客Andrei Pangin(@AndreiPangin)创建的动态附加机制将命令发送到JVM。它很小(24KB),仅需要与JRE一起使用,并支持Linux容器。

用法

在大多数情况下,只需要下载单个文件

wget -L -O /usr/local/bin/jattach \
    https://github.com/apangin/jattach/releases/download/v1.5/jattach && \
    chmod +x /usr/local/bin/jattach

然后我们可以发送dumpheap命令做JVM进程

jattach PID-OF-JAVA dumpheap <path to heap dump file>

例如

java_pid=$(pidof -s java) && \
    jattach $java_pid dumpheap /tmp/java_pid$java_pid-$(date +%Y-%m-%d_%H-%M-%S).hprof

它是如何工作的?

诸如jmapjstack之类的内置JDK实用程序具有两种执行模式:协作模式强制模式

在正常协作模式下,这些工具使用动态附加机制连接到目标 VM。然后,请求的命令由目标 VM 在其自身的进程中执行。jattach 正是使用这种模式。

强制模式(jmap -F,jstack -F)的工作方式则不同。转储工具先挂起目标进程,然后使用 Serviceability Agent 读取进程内存。详见此文

如何在 docker 容器中使用

在 Java 10 jmap 之前,由于附加机制原因,因为与pid和挂载命名空间交互问题,jstackjcmd无法从 host 主机上的进程附加到在Docker 容器内运行的 JVM 上。 Java 10 通过从容器内部由 JVM 找到其在根名称空间的 PID 从而 修复了这个问题,并使用此方法来监视一个附加模式的 JVM。

jattach 支持容器,并且与早期版本的JVM兼容, 我们需要的只是主机PID 名称空间中的进程 ID。我们如何得到它?

如果 JVM 进程是容器的主要进程(PID=1),则所需的信息将包含在docker inspect输出中

cid=<container name or id>
host_pid=$(docker inspect --format {{.State.Pid}} $cid)

如果不是?然后事情变得更加有趣。我知道的最简单的方法是使用 /proc /PID/sched-内核调度统计信息。

cid=<container name or id>
docker exec -it $cid bash -c 'cat /proc/$(pidof -s java)/sched'

java (8251, #threads: 127)
-------------------------------------------------------------------
se.exec_start                                :        275669.207074
se.vruntime                                  :            80.606203
se.sum_exec_runtime                          :            57.897264
nr_switches                                  :                  157
nr_voluntary_switches                        :                  149
nr_involuntary_switches                      :                    8
se.load.weight                               :                 1024
se.avg.load_sum                              :              8883079
se.avg.util_sum                              :                 4424
se.avg.load_avg                              :                  181
se.avg.util_avg                              :                   90
se.avg.last_update_time                      :         275669207074
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                   52
mm->numa_scan_seq                            :                    0
numa_migrations, 0
numa_faults_memory, 0, 0, 1, 0, -1
numa_faults_memory, 1, 0, 0, 0, -1

对我们来说,真正感兴趣的是第一行输出(格式在kernel/sched/debug.c#L877中定义

所需的PID可以通过以下 Shell 脚本提取出来

docker exec -it $cid sh -c 'head -1 /proc/$(pidof -s java)/sched | grep -P "(?<=\()\d+" -o'

当目标容器是裸露的(没有shell,没有cat,什么都没有)时,nsenter 可能是替代docker exec 更好的选择

host_pid=$(docker inspect --format {{.State.Pid}} <container name or id>)
nsenter --target $host_pid  --pid --mount  sh -c 'cat /proc/$(pidof -s java)/sched'

可能的问题

项目发行页面上的Jattach与glibc链接在一起,因此它很可能在Alpine Linux上不起作用。但是使其工作并不难。

容器的 webshell 中如何操作

如果你是通过容器的 webshell 来操作,以下的操作的技巧可能会帮上忙:
使用简单的 top 查看进程和内存使用情况:

top -bn1 

-b不避免生成颜色字符,从而导致部分 web终端无法正常显示。-n1只一次性输出, 而不是间隔刷屏,刷屏在 web 终端上通常也是不支持的。

会输出:

top - 23:27:57 up 78 days,  8:59,  0 users,  load average: 7.11, 5.87, 8.19
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.0 us,  3.4 sy,  0.1 ni, 93.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 26409756+total, 12043812 free, 14782851+used, 10422524+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 12382738+avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    1 root      20   0 6620280 865428  16808 S   1.0  0.3   4:51.06 java
  186 root      20   0   15244   3412   2936 S   0.0  0.0   0:00.17 sh
  329 root      20   0   59336   3980   3496 R   0.0  0.0   0:00.00 top

导出输出的 dump 文件可能会需求 scp 等远程复制软件,可以通过类似下面的命令安装在容器里:

yum install openssh-clients

最终分析 dump 文件可以用:

jhat heap-dump-file

访问 http://localhost:7000


Yujiaao
12.7k 声望4.7k 粉丝

[链接]