1. 项目概述jstatd不是“远程桌面”而是JVM运行时的“听诊器”你有没有遇到过这样的场景线上Java服务突然响应变慢CPU使用率飙升到95%但应用日志里干干净净没有任何ERROR或WARN重启后暂时恢复几小时后又复现。运维同学说“没看到明显异常”开发同学说“本地压测完全没问题”。最后排查三天发现是某段代码在高并发下持续创建短生命周期对象触发了频繁的Minor GC而老年代空间缓慢增长最终导致一次Full GC停顿长达8秒——但整个过程没有任何一行日志主动告诉你“我正在GC”“我快OOM了”“我的Eden区已满99%”。这就是jstatd存在的根本意义它不干预你的程序不修改你的代码不增加任何业务逻辑负担却能像一位经验丰富的ICU医生隔着皮肤、不插管、不抽血仅靠实时采集JVM内部运行时的“生理信号”就判断出内存分配节奏是否失衡、GC策略是否匹配当前负载、类加载是否出现泄漏、线程状态是否陷入阻塞。它不是监控平台不是告警系统更不是可视化大屏——它是JVM进程自身对外暴露的一套标准化“健康数据接口”的守护者与转发者。核心关键词“jstatd”三个字母背后实际承载着三层不可替代的价值第一层是协议桥接——它把JVM本地通过RMI暴露的JMX管理端口默认1099转换成可被局域网内任意jstat客户端识别的、无状态的、轻量级的统计服务第二层是权限穿透——它绕过了JVM默认的本地绑定限制即jstat只能连本机让远程诊断成为可能第三层是零侵入性——你不需要改启动参数加-agentlib不需要引入Spring Boot Actuator依赖甚至不需要重启应用只要JVM以-Dcom.sun.management.jmxremote等基础参数启动jstatd就能立刻开始工作。这个工具特别适合三类人一是刚接手遗留系统的Java开发面对几十个Tomcat实例不知从哪台查起二是没有部署PrometheusGrafana监控体系的中小团队需要快速定位单点性能瓶颈三是做JVM调优验证的技术人员比如想对比开启ZGC前后Young GC频率变化但又不想在每台机器上手动敲十遍jstat命令。它解决的从来不是“要不要监控”的问题而是“如何用最原始、最可靠、最不依赖外部组件的方式拿到最真实的JVM心跳数据”。2. jstatd设计原理与架构拆解为什么必须用它而不是直接jstat -host2.1 jstat命令的本质一个“只读客户端”而非“远程代理”很多人第一次尝试远程监控JVM时会自然想到既然jstat可以查看本地JVM那加个-host参数不就能看远程了吗于是执行jstat -gc 192.168.1.100:1099 1000 5结果报错Could not connect to 192.168.1.100:1099. Connection refused.或者更隐蔽的错误java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: java.net.ConnectException: Connection refused这里暴露出一个关键认知误区jstat本身不具备RMI客户端能力它依赖的是JVM进程内部的“Attach API”机制。该机制在Linux下通过/tmp/.java_pidpid文件通信在Windows下通过命名管道全程走的是本地进程间通信IPC根本不经过网络协议栈。也就是说jstat命令的设计初衷就是“只连本机”它的源码里压根没有socket连接远程主机的逻辑。那么问题来了如果jstat不能连远程为什么官方文档里又提到“jstat supports remote monitoring”答案就在jstatd身上——它才是那个真正实现远程能力的“中间人”。2.2 jstatd的核心职责RMI注册表的“守门人”与“翻译官”jstatd的全称是Java Statistics Daemon但它既不收集数据也不存储数据更不分析数据。它只做两件事启动一个RMI Registry注册中心默认监听1099端口向该Registry注册一个名为jstatd的远程对象这个对象实现了sun.tools.jstatd.JStatdMBean接口本质是一个“JVM统计数据代理”。当jstatd启动后它会扫描本机所有符合要求的Java进程即开启了JMX远程管理的JVM并为每个进程建立一条“观测通道”。这条通道的建立过程如下jstatd通过java.lang.management.ManagementFactory.getRuntimeMXBean().getName()获取目标JVM的PID再通过com.sun.tools.attach.VirtualMachine.attach(pid)附加到该JVM注意这步仍是本地IPC不走网络附加成功后jstatd调用目标JVM的sun.management.HotSpotDiagnostic和java.lang.management.MemoryUsage等内部API实时读取GC次数、内存池使用量、类加载数量等原始指标最后jstatd将这些原始数据封装成RMI可序列化的对象发布到自己启动的RMI Registry中供外部jstat客户端按需拉取。提示jstatd本身不监听任何HTTP端口也不提供Web界面。它只是一个纯粹的RMI服务端所有通信都基于Java RMI协议底层走TCP。这意味着它天然支持跨平台Linux/Windows/macOS但也意味着防火墙必须放行RMI Registry端口默认1099以及RMI动态分配的回调端口通常在32768–65535之间。2.3 为什么不能跳过jstatd直接让jstat连JVM的JMX端口这是实操中最常被问到的问题。答案很明确因为jstat和JMX是两套完全不兼容的协议体系。JMXJava Management Extensions是一套通用的Java资源管理规范它定义了MBeanManaged Bean模型、远程访问方式RMI或IIOP、安全认证机制等功能强大但相对重量级jstat协议则是HotSpot JVM私有的一套极简统计协议它不涉及MBean注册、不支持属性读写、不提供操作方法只提供固定格式的只读统计快照如-gc、-compiler、-class输出的列字段都是硬编码的。你可以把JMX想象成医院的HIS系统包含患者档案、检查报告、医嘱开立等完整功能而jstat协议就像心电监护仪屏幕上跳动的波形图——它只显示当前心跳频率、QRS波振幅、ST段偏移但不会告诉你病人姓名、病史、用药记录。jstatd的作用就是把心电监护仪的模拟信号转换成数字信号并接入医院的中央监护网络让护士站的大屏也能看到同一组波形。所以即使你配置了-Dcom.sun.management.jmxremote.port9999jstat也无法直连9999端口因为它根本不懂JMX的RMI stub序列化格式也不认识java.lang:typeMemory这样的ObjectName。强行连接只会得到MalformedObjectNameException或NotCompliantMBeanException。2.4 jstatd的权限模型为什么它能“看到”其他JVM而普通jstat不能这个问题触及Java安全管理的核心。jstatd之所以能附加attach到其他JVM进程依赖于两个关键前提操作系统层面的进程权限在Linux上jstatd进程必须与目标JVM进程属于同一用户或拥有root权限。这是因为/proc/pid/目录下的文件如/proc/pid/fd/默认只有进程所有者可读。jstatd正是通过读取/proc/pid/fd/中的socket文件描述符找到目标JVM的本地管理套接字路径通常是/tmp/.java_pidpid再通过Unix Domain Socket完成IPC通信。JVM启动参数的显式授权目标JVM必须启用-Dcom.sun.management.jmxremote系列参数其中最关键的是-Dcom.sun.management.jmxremote.authenticatefalse \ -Dcom.sun.management.jmxremote.sslfalse \ -Djava.rmi.server.hostname192.168.1.100这里java.rmi.server.hostname尤其重要——它告诉JVM“当有RMI客户端连接我时请把我的RMI stub地址写成这个IP而不是默认的127.0.0.1”。如果没有设置jstatd虽然能附加成功但在向RMI Registry注册远程对象时会把回调地址写成127.0.0.1:xxxx导致外部jstat客户端连上jstatd后再尝试连接这个127.0.0.1地址时失败因为那是jstatd所在机器的localhost不是目标JVM所在机器。注意jstatd本身不需要配置JMX参数它不对外提供JMX服务只作为JVM的“本地观察员”存在。它的安全性完全由目标JVM的JMX配置决定。这也是为什么生产环境建议始终开启authenticatetrue哪怕只用简单的用户名密码——因为一旦jstatd被恶意利用攻击者就能获取完整的JVM运行时内存快照包括字符串常量池中的敏感信息如数据库密码明文拼接。3. 实操全流程详解从零部署jstatd到远程诊断实战3.1 环境准备与版本兼容性确认jstatd是JDK自带工具位于$JAVA_HOME/bin/目录下无需额外安装。但版本兼容性至关重要不同JDK版本的jstatd与目标JVM之间存在严格的二进制协议约束jstatd所在JDK版本可监控的目标JVM版本关键限制说明JDK 8u202JDK 8u202及以下完全兼容推荐组合JDK 11.0.12JDK 11.0.12及以下支持ZGC、Shenandoah等新GC日志格式JDK 17.0.2JDK 17.0.2及以下支持JFRJava Flight Recorder事件导出跨大版本禁止JDK 8 ↔ JDK 11jstatd会抛出UnsupportedClassVersionError因内部序列化类结构变更实操前务必执行# 查看jstatd版本即JDK版本 jstatd -J-version # 查看目标JVM版本登录目标服务器执行 ps aux | grep java | grep -v grep # 找到启动命令提取 -version 参数或直接执行 /opt/jdk1.8.0_292/bin/java -version实操心得我曾在一个混合JDK环境部分服务用JDK 8部分用JDK 11中踩过坑——运维同学统一用JDK 11的jstatd去监控所有JVM结果JDK 8的服务全部无法被发现。排查三天才发现是sun.jvmstat.perfdata.monitor.protocol.local.LocalVmManager类在JDK 11中已被重构无法解析JDK 8生成的/tmp/hsperfdata_user/文件格式。最终解决方案是在每台目标服务器上用其对应版本的JDK启动jstatd即JDK 8服务器跑JDK 8的jstatdJDK 11服务器跑JDK 11的jstatd。3.2 jstatd服务端部署四步完成稳定监听步骤1创建专用jstatd用户与安全目录为避免权限泛滥绝不允许用root用户运行jstatd。创建独立用户# 创建用户组和用户 sudo groupadd jstatd sudo useradd -g jstatd -m -s /bin/bash jstatd sudo passwd jstatd # 设置密码 # 创建jstatd工作目录属主设为jstatd sudo mkdir -p /opt/jstatd/{logs,conf} sudo chown -R jstatd:jstatd /opt/jstatd提示/opt/jstatd/conf/用于存放jstatd.policy安全策略文件这是JDK 8强制要求的。如果不配置jstatd启动时会报AccessControlException拒绝附加到其他JVM。步骤2编写jstatd.policy安全策略文件jstatd默认启用Java安全管理器Security Manager必须显式授权才能执行attach操作。创建/opt/jstatd/conf/jstatd.all.policygrant codeBase file:${java.home}/lib/tools.jar { permission java.security.AllPermission; }; grant codeBase file:/opt/jstatd/- { permission java.security.AllPermission; };该策略授予tools.jar包含VirtualMachine类和jstatd工作目录下所有代码AllPermission确保无权限拦截。生产环境如需最小权限可精确到permission java.lang.RuntimePermission createClassLoader; permission java.lang.RuntimePermission getClassLoader; permission java.lang.RuntimePermission setContextClassLoader; permission java.util.PropertyPermission java.home, read; permission java.io.FilePermission /tmp/hsperfdata_-, read; permission java.io.FilePermission /proc/-/fd/-, read;步骤3编写jstatd启动脚本创建/opt/jstatd/start-jstatd.sh#!/bin/bash # jstatd启动脚本适配JDK 8/11/17 JSTATD_HOME/opt/jstatd JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 # 根据实际JDK路径修改 JSTATD_PORT1099 JSTATD_RMI_PORT2099 # 显式指定RMI回调端口避免随机端口被防火墙拦截 # 检查JDK版本 JDK_VERSION$($JAVA_HOME/bin/java -version 21 | head -1 | cut -d -f2 | cut -d. -f1,2) echo Starting jstatd with JDK $JDK_VERSION # 启动jstatd关键参数说明 # -p 指定RMI Registry端口默认1099 # -nr 表示不自动启动RMI Registry我们手动控制 # -J-Djava.security.policy 指定安全策略文件 # -J-Djava.rmi.server.hostname 必须设为目标服务器真实IP否则远程jstat无法回调 $JAVA_HOME/bin/jstatd \ -p $JSTATD_PORT \ -nr \ -J-Djava.security.policy$JSTATD_HOME/conf/jstatd.all.policy \ -J-Djava.rmi.server.hostname192.168.1.100 \ -J-Dcom.sun.management.jmxremote.rmi.port$JSTATD_RMI_PORT \ -J-Djava.rmi.server.useLocalHostnametrue \ -J-Xms64m -J-Xmx128m \ $JSTATD_HOME/logs/jstatd.log 21 echo $! $JSTATD_HOME/jstatd.pid echo jstatd started with PID $(cat $JSTATD_HOME/jstatd.pid)实操心得-J-Dcom.sun.management.jmxremote.rmi.port这个参数极其关键它强制jstatd使用固定端口如2099作为RMI回调端口而不是默认的随机高端口32768。这样防火墙只需开放两个端口1099和2099运维同事就不会抱怨“每次都要临时开一堆端口”。我在某次金融客户巡检中就是因为没加这个参数导致安全团队拒绝开通策略整个监控方案搁置两周。步骤4配置systemd服务并开机自启创建/etc/systemd/system/jstatd.service[Unit] Descriptionjstatd Service Afternetwork.target [Service] Typeforking Userjstatd Groupjstatd EnvironmentJAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 ExecStart/opt/jstatd/start-jstatd.sh ExecStop/bin/sh -c kill $(cat /opt/jstatd/jstatd.pid) 2/dev/null || true Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable jstatd sudo systemctl start jstatd sudo systemctl status jstatd # 检查是否active (running)3.3 目标JVM配置让jstatd“看得见、连得上”jstatd能工作前提是目标JVM主动“亮出身份”。以下是生产环境验证过的最小可行配置以Tomcat为例在$CATALINA_HOME/bin/catalina.sh中添加# 在JAVA_OPTS中追加以下参数注意必须在启动前设置不能热加载 JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote.port9999 JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote.rmi.port9999 # 与上面端口一致简化防火墙策略 JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticatefalse JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote.sslfalse JAVA_OPTS$JAVA_OPTS -Djava.rmi.server.hostname192.168.1.200 # 目标服务器真实IP非127.0.0.1 JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote.local.onlyfalse # 允许远程连接注意事项jmxremote.port和jmxremote.rmi.port设为相同值可避免RMI Registry与回调端口分离带来的复杂网络策略java.rmi.server.hostname必须填写目标服务器在局域网内的真实IP如192.168.1.200绝对不能写localhost或127.0.0.1否则jstatd注册的stub地址会是127.0.0.1:9999远程jstat客户端连过去就失败如果目标服务器有多网卡如eth0内网、eth1公网务必确认java.rmi.server.hostname指向的是与jstatd服务器互通的网卡IP生产环境强烈建议开启authenticatetrue并配置jmxremote.password.file和jmxremote.access.file但需同步修改jstatd启动参数增加-J-Dcom.sun.management.jmxremote.password.file...。3.4 远程客户端实操用jstat命令连接jstatd假设jstatd部署在192.168.1.100:1099目标JVM在192.168.1.200上运行PID为12345。在你的本地开发机或监控服务器执行# 第一步确认jstatd服务可达 telnet 192.168.1.100 1099 # 应显示Connected # 第二步列出jstatd已发现的所有JVM进程无需知道PID jstat -list 192.168.1.100:1099 # 输出示例 # 12345 sun.tools.jstatd.JStatd # 这就是目标JVM的PID和进程名 # 第三步实时监控GC行为每2秒刷新一次共10次 jstat -gc 192.168.1.100:1099 12345 2000 10 # 输出列说明JDK 8 # S0C: Survivor0区容量(KB) | S0U: Survivor0区已用(KB) | EC: Eden区容量 | EU: Eden区已用 | OC: 老年代容量 | OU: 老年代已用 | MC: 元空间容量 | MU: 元空间已用 | YGC: Young GC次数 | YGCT: Young GC耗时(s) | FGC: Full GC次数 | FGCT: Full GC耗时 | GCT: 总GC耗时实操技巧jstat输出是纯文本流非常适合用awk做实时分析。例如监控Eden区使用率是否持续高于90%jstat -gc 192.168.1.100:1099 12345 1000 | awk NR1 {edenu$3; edenc$2; if(edenc!0 edenu/edenc*10090) print ALERT: Eden usage 90% at strftime(%H:%M:%S)}4. 常见问题与排查技巧实录那些官方文档不会写的坑4.1 “jstat -list 返回空列表”——九成问题出在这三个地方这是新手遇到最多的问题。当你执行jstat -list 192.168.1.100:1099终端一片空白既不报错也不输出。别急按以下顺序逐项排查排查项检查命令预期结果问题定位jstatd进程是否存活ps aux | grep jstatd | grep -v grep应显示jstatd进程及其PID若无输出说明jstatd未启动或已崩溃jstatd日志是否有Attach失败记录tail -20 /opt/jstatd/logs/jstatd.log应有Attaching to VM pid或No attach mechanism available若出现后者说明目标JVM未开启JMX或jstatd用户无权限读取/proc/pid/fd/目标JVM是否真的在jstatd所在机器上运行ssh jstatd192.168.1.100 ps aux | grep java | grep -v jstatd应列出目标JVM进程含完整启动命令若无输出说明目标JVM根本不在该机器或jstatd用户无权查看其他用户进程实操心得有一次客户反馈“jstatd完全不工作”我远程登录后发现他们的运维同学把jstatd部署在一台独立的监控服务器上而目标JVM全在K8s集群内——jstatd根本连不到容器里的JVM进程因为/proc是宿主机视角容器内PID在宿主机上是另一个数字。正确做法是在每个K8s Node上以DaemonSet方式部署jstatd监听本机所有容器的JVM。4.2 “Connection refused to host: 127.0.0.1”——经典的RMI地址错乱错误信息典型如java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: java.net.ConnectException: Connection refused (Connection refused)这表示jstatd向RMI Registry注册时把回调地址写成了127.0.0.1而你的jstat客户端试图连接这个地址自然失败。根本原因只有一个目标JVM启动时未设置-Djava.rmi.server.hostname。验证方法# 在目标JVM服务器上查找其启动参数中是否包含hostname ps aux | grep java | grep -o java.rmi.server.hostname[^ ]* # 若无输出或输出为 hostname127.0.0.1则确认问题修复方案修改目标JVM启动脚本强制指定真实IP# 不要写成 # -Djava.rmi.server.hostnamelocalhost # 而要写成 -Djava.rmi.server.hostname$(hostname -I | awk {print $1})提示hostname -I命令返回所有IPv4地址awk {print $1}取第一个通常是内网IP。比硬编码IP更健壮适用于自动化部署。4.3 “jstatd启动报错Port already in use”——端口冲突的静默杀手jstatd默认使用1099端口启动RMI Registry但很多系统服务如某些Oracle数据库组件、旧版Jenkins也默认占用1099。jstatd不会友好提示“端口被占”而是直接退出日志里只有一行java.rmi.server.ExportException: Port already in use。排查命令# 查看1099端口被谁占用 sudo lsof -i :1099 # 或 sudo netstat -tulnp | grep :1099解决方案有两种换端口在jstatd启动命令中加-p 1100同时在jstat客户端连接时用jstat -list 192.168.1.100:1100杀掉冲突进程sudo kill -9 PID谨慎操作先确认该进程非关键服务。实操心得我见过最诡异的案例——某公司IDC的交换机ACL策略把1099端口默认重定向到内部监控系统。jstatd明明启动成功但外部jstat客户端连上去后收到的却是交换机返回的HTTP 404页面。最后用tcpdump抓包才定位到问题。所以当一切配置都正确却仍失败时务必用telnet ip port和tcpdump -i any port 1099双验证。4.4 “jstat输出数据全是0或N/A”——指标采集失效的隐性故障现象jstat -gc命令能连上PID也正确但输出的ECEden容量、EUEden已用等列全是0.0或N/A而YGCYoung GC次数却在正常增长。这说明jstatd成功附加到了JVM但无法读取/tmp/hsperfdata_user/目录下的性能数据文件。常见原因目标JVM以-XX:UseContainerSupport运行Docker/K8s环境且容器未挂载/tmp目录。JVM默认把hsperfdata文件写入容器内的/tmp而jstatd在宿主机上读取的是宿主机的/tmp路径不一致。解决方案启动容器时显式挂载/tmpdocker run -v /tmp:/tmp your-java-app目标JVM设置了-XX:PerfDisableSharedMem禁用了性能计数器共享内存机制。此时JVM不会生成hsperfdata_pid文件。解决方案移除该JVM参数或改用JFRJava Flight Recorder替代。/tmp目录权限问题/tmp/hsperfdata_user目录属主是目标JVM用户而jstatd用户无读取权限。解决方案在目标JVM启动脚本中加-XX:UnlockCommercialFeatures -XX:FlightRecorderJDK 8u212或升级到JDK 11使用jcmd pid VM.native_memory summary替代。4.5 防火墙与SELinux企业级环境的隐形壁垒在CentOS/RHEL系统上即使所有配置正确仍可能因SELinux阻止jstatd的网络通信。症状是jstatd进程存活日志无报错但jstat -list超时。检查SELinux状态sestatus # 若为enforcing需放行端口临时放行重启后失效sudo setsebool -P allow_java_execmem 1 sudo semanage port -a -t jmx_port_t -p tcp 1099 sudo semanage port -a -t jmx_port_t -p tcp 2099永久放行推荐# 编辑SELinux策略模块 echo module jstatd 1.0; require { type initrc_t; type jmx_port_t; class tcp_socket name_bind; } allow initrc_t jmx_port_t:tcp_socket name_bind; jstatd.te checkmodule -M -m -o jstatd.mod jstatd.te semodule_package -o jstatd.pp -m jstatd.mod sudo semodule -i jstatd.pp提示对于严格遵循等保要求的客户建议将jstatd部署在与业务服务器同网段的专用监控节点而非直接在业务服务器上运行以满足“监控与业务网络隔离”的审计要求。5. 进阶技巧与生产实践让jstatd不止于“能用”5.1 自动化发现用shell脚本批量监控多台服务器手动记IP和PID太低效。以下脚本可自动扫描网段内所有运行jstatd的服务器并列出其监控的JVM#!/bin/bash # scan-jstatd.sh - 扫描192.168.1.0/24网段 NETWORK192.168.1 JSTATD_PORT1099 for i in {1..254}; do IP${NETWORK}.${i} # 检查jstatd端口是否开放 if timeout 1 bash -c echo /dev/tcp/$IP/$JSTATD_PORT 2/dev/null; then echo Found jstatd on $IP # 获取该jstatd发现的所有JVM jstat -list $IP:$JSTATD_PORT 2/dev/null | while read line; do if [[ -n $line $line ! No agents available ]]; then PID$(echo $line | awk {print $1}) NAME$(echo $line | awk {$1; print $0} | sed s/^ //) echo PID: $PID | Process: $NAME # 附带GC摘要 jstat -gc $IP:$JSTATD_PORT $PID 1000 1 2/dev/null | tail -1 | awk {printf Eden: %.1f%%, Old: %.1f%%, YGC: %d\n, $3/$2*100, $5/$4*100, $10} fi done echo fi done运行效果 Found jstatd on 192.168.1.100 PID: 12345 | Process: /opt/tomcat/bin/bootstrap.jar Eden: 87.2%, Old: 42.1%, YGC: 152 Found jstatd on 192.168.1.101 PID: 67890 | Process: /opt/app/myapp.jar Eden: 12.5%, Old: 5.3%, YGC: 85.2 与Grafana集成用jstatd数据驱动可视化看板jstatd本身无HTTP接口但可通过jstat命令输出转为Prometheus指标。方案如下编写Python脚本jstat_exporter.py定时执行jstat -gc并解析为Prometheus文本格式用textfile_collector将指标写入/var/lib/node_exporter/textfile_collector/Prometheus配置抓取该目录Grafana创建看板展示GC频率、内存使用率趋势。核心解析逻辑Pythonimport subprocess import time from prometheus_client import CollectorRegistry, Gauge, write_to_textfile def get_jstat_metrics(host, port, pid): cmd fjstat -gc {host}:{port} {pid} 1000 1 try: output subprocess.check_output(cmd, shellTrue, stderrsubprocess.STDOUT).decode() lines output.strip().split(\n) if len(lines) 2: return {} headers lines[0].split() values lines[1].split() metrics {} for i, h in enumerate(headers): if h in [S0C, S0U, EC, EU, OC, OU, MC, MU, YGC, YGCT, FGC, FGCT, GCT]: metrics[h] float(values[i]) if values[i] ! . else 0.0 return metrics except Exception as e: print(fjstat failed: {e}) return {} # 示例采集192.168.1.100:1099上的PID 12345 metrics get_jstat_metrics(192.168.1.100, 1099, 12345) if metrics: registry CollectorRegistry() gc_count Gauge(jvm_gc_young_count, Young GC count, registryregistry) gc_count.set(metrics.get(YGC, 0)) mem_eden_used Gauge(jvm_memory_eden_used_kb, Eden space used (KB), registryregistry) mem_eden_used.set(metrics.get(EU, 0)) write_to_textfile(/var/lib/node_exporter/textfile_collector/jvm_stats.prom, registry)5.3 故障回溯用jstatd日志还原GC风暴发生时刻jstatd本身不记录历史数据但它的日志