原作者: Jarek Przygódzki (http://twitter.com/jarekprzyg... (http://github.com/jarek-przyg...
用 JDK 生成 JVM 堆内存转储非常简单,因为几乎每个Java开发人员都知道 JDK 附带的jmap
和jcmd
工具。但是 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
例如
java_pid=$(pidof -s java) && \
jattach $java_pid dumpheap /tmp/java_pid$java_pid-$(date +%Y-%m-%d_%H-%M-%S).hprof
它是如何工作的?
诸如jmap
和jstack
之类的内置JDK实用程序具有两种执行模式:协作模式和强制模式。
在正常协作模式下,这些工具使用动态附加机制连接到目标 VM。然后,请求的命令由目标 VM 在其自身的进程中执行。jattach
正是使用这种模式。
强制模式(jmap -F,jstack -F)的工作方式则不同。转储工具先挂起目标进程,然后使用 Serviceability Agent 读取进程内存。详见此文。
如何在 docker 容器中使用
在 Java 10 jmap 之前,由于附加机制原因,因为与pid和挂载命名空间交互问题,jstack
和jcmd
无法从 host 主机上的进程附加到在Docker 容器内运行的 JVM 上。 Java 10 通过从容器内部由 JVM 找到其在根名称空间的 PID 从而 修复了这个问题,并使用此方法来监视一个附加模式的 JVM。
jattach
支持容器,并且与早期版本的JVM兼容, 我们需要的只是主机PID 名称空间中的进程 ID。我们如何得到它?
如果 JVM 进程是容器的主要进程(PID=1),则所需的信息将包含在docker inspect
输出中
cid=
host_pid=$(docker inspect --format {{.State.Pid}} $cid)
如果不是?然后事情变得更加有趣。我知道的最简单的方法是使用 /proc /PID/sched
-内核调度统计信息。
cid=
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}} )
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