jstat

JVM监控是Java应用运维的核心环节,通过jstat、jmap系列命令可实现GC实时监控、内存配置核查、对象占用分析及堆快照dump。本文针对宿主机与Docker容器两种场景,详细拆解各命令用法、输出解读、容器化适配技巧及注意事项,重点强化Docker环境下的权限、进程隔离、日志持久化等关键要点,适用于JDK8及以上版本。

一、实时监控JVM GC情况(jstat -gcutil)

jstat -gcutil命令用于轻量级实时采集JVM各内存区域使用率、GC次数及耗时,是排查GC频繁、耗时过长的首选工具,宿主机与Docker场景用法存在差异,需针对性适配。

  1. 基础语法(通用)
jstat -gcutil <pid> <interval> <count>
  • pid:Java进程ID(宿主机为系统进程ID,Docker容器内为容器内进程ID,通常为1);
  • interval:监控间隔,单位毫秒(如1000表示每秒采集1次);
  • count:采集次数(省略则持续采集,需手动Ctrl+C终止)。
  1. 宿主机场景实操
# 1. 获取Java进程ID(以Tomcat为例)
ps -ef | grep java | grep -v grep  # 过滤出目标进程ID,如12345

# 2. 执行监控命令,每秒采集1次,共100次
jstat -gcutil 12345 1000 100

# 3. 日志重定向(便于后续分析)
jstat -gcutil 12345 1000 100 > /var/log/jvm/gc.log 2>&1
    
  1. Docker容器场景实操(核心重点)

Docker容器内进程与宿主机隔离,无法直接通过宿主机进程ID监控,需进入容器或通过docker exec命令执行,步骤如下:

# 场景1:进入容器内执行(交互模式)
# 1. 查看容器列表,获取目标容器名/ID(以tomcat容器为例)
docker ps | grep tomcat  # 假设容器名为tomcat-app,ID为abc123

# 2. 进入容器(--user root避免权限不足)
docker exec -it --user root tomcat-app /bin/bash

# 3. 容器内获取Java进程ID(单一应用容器通常为1)
ps -ef | grep java | grep -v grep  # 输出进程ID为1

# 4. 执行监控命令(容器内JDK路径通常为/opt/java/openjdk/bin/,需确认路径)
/opt/java/openjdk/bin/jstat -gcutil 1 1000 100

# 场景2:宿主机直接执行(非交互模式,推荐运维自动化)
# 直接调用容器内jstat,日志输出到宿主机
docker exec --user root tomcat-app /opt/java/openjdk/bin/jstat -gcutil 1 1000 100 > /var/log/jvm/gc-container.log 2>&1

# 场景3:后台持续监控(避免终端阻塞)
nohup docker exec --user root tomcat-app /opt/java/openjdk/bin/jstat -gcutil 1 1000 > /var/log/jvm/gc-container.log 2>&1 &
    
  1. 输出结果解读
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   5.23  68.91  42.15  91.32  89.45    236    3.782     12    2.156    5.938
字段 含义 关键监控阈值
S0/S1 Survivor 0/1区使用率(%) 交替空闲,无固定阈值,需结合GC频率判断
E Eden区使用率(%) 超过85%频繁触发Minor GC,需检查新生代大小
O 老年代使用率(%) 超过75%易触发Full GC,超过90%存在内存风险
M 元空间使用率(%) 超过90%需检查类加载是否异常(如类泄漏)
YGC/FGC Minor GC/Full GC总次数 Full GC每小时超过2次需排查原因
YGCT/FGCT Minor GC/Full GC总耗时(秒) Full GC单次耗时>1秒影响业务响应
  1. 注意事项
  • Docker场景需确认容器内JDK路径,不同镜像路径可能不同(如官方Tomcat镜像为/opt/java/openjdk/bin/,自定义镜像可能为/usr/local/jdk/bin/);
  • 容器内执行命令需保证root权限,否则可能无法读取进程信息;
  • 监控间隔不宜过短(≥500ms),避免增加容器内CPU负担。

二、查看JVM堆内存配置信息(jmap -heap)

jmap -heap命令用于查看Java进程的堆内存整体配置、各区域大小及使用情况,可快速验证JVM启动参数(-Xms、-Xmx等)是否生效,适配宿主机与Docker场景。

  1. 基础语法(通用)
jmap -heap <pid>
  1. 宿主机场景实操
# 1. 获取Java进程ID
ps -ef | grep java | grep -v grep  # 如12345

# 2. 执行命令查看配置
jmap -heap 12345
# 输出包含堆配置(-Xms/-Xmx/-Xmn等)、各区域使用情况、GC回收器类型
    
  1. Docker容器场景实操
# 方式1:进入容器内执行
docker exec -it --user root tomcat-app /bin/bash
/opt/java/openjdk/bin/jmap -heap 1  # 容器内Java进程ID为1

# 方式2:宿主机直接执行,输出到文件便于查看
docker exec --user root tomcat-app /opt/java/openjdk/bin/jmap -heap 1 > /var/log/jvm/jvm-heap-config.log 2>&1
cat /var/log/jvm/jvm-heap-config.log
    
  1. 核心输出解读(关键部分)
Heap Configuration:  # JVM堆配置(对应启动参数)
   MaxHeapSize              = 2147483648 (2048.0MB)  # -Xmx配置值
   NewSize                  = 536870912 (512.0MB)     # -Xmn配置值
   SurvivorRatio            = 8                      # -XX:SurvivorRatio配置值
   MetaspaceSize            = 67108864 (64.0MB)      # -XX:MetaspaceSize配置值
Heap Usage:  # 实际使用情况
New Generation (Eden + 1 Survivor Space):
   capacity = 482344960 (460.0MB)
   used     = 235929600 (225.0MB)
   48.91% used
tenured generation:  # 老年代
   capacity = 1610612736 (1536.0MB)
   used     = 655360000 (625.0MB)
   40.69% used
    
  1. 注意事项
  • 该命令会触发短暂STW(停止世界),Docker容器内执行需避开业务峰值,避免影响容器内应用响应;
  • 若容器内提示“Permission denied”,需添加--user root参数进入容器,确保权限充足;
  • 可通过输出对比配置值与实际使用情况,排查JVM参数配置错误(如参数未生效、配置不合理)。

三、查看JVM类及对象占用情况(jmap -histo)

jmap -histo命令用于统计堆内存中各类的实例数量、占用内存大小,可快速定位大对象、频繁创建的对象,为排查内存泄漏、优化对象创建逻辑提供依据,支持存活对象过滤。

  1. 基础语法(通用)
# 统计所有对象
jmap -histo <pid> | head -n <行数>  # head筛选前N行,避免输出过多

# 仅统计存活对象(触发Full GC,谨慎使用)
jmap -histo:live <pid> | head -n <行数>
    
  1. 宿主机场景实操(示例:查看前20类)
ps -ef | grep java | grep -v grep  # 进程ID 12345
jmap -histo 12345 | head -20
    
  1. Docker容器场景实操
# 方式1:进入容器内查看前20类
docker exec -it --user root tomcat-app /bin/bash
/opt/java/openjdk/bin/jmap -histo 1 | head -20

# 方式2:宿主机导出结果到文件
docker exec --user root tomcat-app /opt/java/openjdk/bin/jmap -histo 1 | head -50 > /var/log/jvm/jvm-object-stat.log 2>&1

# 方式3:统计容器内存活对象(触发Full GC,线上避峰)
docker exec --user root tomcat-app /opt/java/openjdk/bin/jmap -histo:live 1 | head -50 > /var/log/jvm/jvm-live-object-stat.log 2>&1
    
  1. 输出结果解读
 num     #instances         #bytes  class name
----------------------------------------------
   1:         9876      13543680  com.example.Order  # 自定义业务类
   2:        15632       9214560  java.lang.String   # 字符串对象
   3:         6789       7654320  java.util.HashMap  # 集合对象
   4:         4321       5432160  [C                  # char数组(字符串底层)
    
  • #instances:实例数量,异常过多(如百万级)需排查对象是否未释放;
  • #bytes:占用内存(字节),换算为MB(除以1024*1024),大对象需重点分析;
  • class name:类全名,[C为char数组、[B为byte数组,自定义类占用过高需排查业务逻辑。
  1. 注意事项
  • 添加:live参数会触发Full GC,回收无用对象,确保统计准确性,但会产生STW,Docker容器内执行需严格避峰;
  • 容器内对象统计结果仅反映容器内堆状态,与宿主机无关,需结合业务场景判断是否存在内存泄漏;
  • 建议多次执行对比结果,若某类对象数量持续增长,大概率存在内存泄漏。

四、堆内存dump及离线分析(jmap -dump)

jmap -dump命令用于生成堆内存完整快照(包含所有对象及引用关系),结合Jvisualvm、MAT工具可精准定位内存泄漏、大对象引用链等复杂问题,Docker场景需解决快照文件导出问题。

  1. 基础语法(通用)
# 生成完整堆快照
jmap -dump:format=b,file=<文件名>.hprof <pid>

# 生成存活对象堆快照(减少文件体积)
jmap -dump:live,format=b,file=<文件名>.hprof <pid>
    
  • format=b:二进制格式,工具仅支持此格式;
  • file:快照文件路径及名称,后缀建议为.hprof。
  1. 宿主机场景实操
ps -ef | grep java | grep -v grep  # 进程ID 12345
jmap -dump:format=b,file=/var/log/jvm/jvm-heap-dump.hprof 12345

# 用Jvisualvm分析(本地启动)
jvisualvm  # 装入生成的.hprof文件,分析引用链及泄漏点
    
  1. Docker容器场景实操(核心:快照导出宿主机)

Docker容器内生成的快照文件需导出到宿主机,否则容器删除后文件丢失,两种实现方式如下:

# 方式1:先在容器内生成快照,再复制到宿主机
# 1. 进入容器生成快照
docker exec -it --user root tomcat-app /bin/bash
/opt/java/openjdk/bin/jmap -dump:format=b,file=/tmp/jvm-heap-dump.hprof 1

# 2. 宿主机执行复制命令,将容器内文件导出
docker cp tomcat-app:/tmp/jvm-heap-dump.hprof /var/log/jvm/

# 3. (可选)删除容器内临时文件,释放空间
docker exec --user root tomcat-app rm -rf /tmp/jvm-heap-dump.hprof

# 方式2:通过数据卷挂载,直接生成到宿主机(推荐长期使用)
# 1. 启动容器时挂载宿主机目录到容器内(如/var/log/jvm:/tmp/jvm)
docker run -d -v /var/log/jvm:/tmp/jvm --name tomcat-app tomcat:8.5

# 2. 直接生成快照到挂载目录(自动同步到宿主机)
docker exec --user root tomcat-app /opt/java/openjdk/bin/jmap -dump:format=b,file=/tmp/jvm/jvm-heap-dump.hprof 1
    
  1. 离线分析工具使用

(1)Jvisualvm(简单易用,适合快速排查)

  1. 启动Jvisualvm(JDK自带,命令行输入jvisualvm);
  2. 点击「文件」→「装入」,选择导出到宿主机的.hprof文件;
  3. 通过「堆Dump」标签查看对象分布,「内存泄漏检测」功能自动识别泄漏可疑点。

(2)MAT(功能强大,适合复杂内存泄漏)

  1. 下载安装MAT(官网:https://www.eclipse.org/mat/);

  2. 打开.hprof文件,自动生成分析报告;

  3. 通过「Leak Suspects」定位泄漏根源,「Dominator Tree」查看大对象引用链。

  4. 注意事项

  • 堆快照文件体积接近堆内存大小(如2G堆生成2G左右文件),需预留足够磁盘空间,Docker场景避免占用容器内存储空间;
  • 生成快照会触发STW,耗时与堆大小相关(2G堆约5-15秒),线上需在备机复现或业务低峰期执行;
  • Docker数据卷挂载需注意权限,宿主机目录需赋予777权限(chmod 777 /var/log/jvm),避免容器内无法写入。

五、Docker注意事项

  1. 进程隔离原则:容器内进程ID与宿主机完全独立,宿主机工具无法直接访问容器内进程,所有命令需通过docker exec执行或进入容器操作;
  2. JDK路径一致性:容器内JDK路径需提前确认,不同镜像路径差异较大,可通过docker exec容器执行「which java」查询;
  3. 权限管控:默认用户可能无权限访问进程信息,执行命令需添加--user root参数,确保root权限;
  4. 日志/快照持久化:容器内临时文件易丢失,建议通过数据卷挂载宿主机目录,将监控日志、堆快照直接生成到宿主机;
  5. 性能影响控制:触发STW的命令(jmap -heap、jmap -histo:live、jmap -dump)需严格避峰,容器化环境资源隔离,STW过长可能导致容器被Docker杀死;
  6. 工具缺失处理:部分精简镜像可能缺少jstat、jmap工具,可在Dockerfile中基于原镜像安装完整JDK,或从宿主机复制工具包到容器内。
posted @ 2026-01-28 16:37  向闲而过  阅读(1)  评论(0)    收藏  举报