JDK jstat 命令详解:基于 jvmstat 框架的 JVM 实时统计监控
2026/9/13 23:51:51 网站建设 项目流程

JDK jstat 命令详解:基于 jvmstat 框架的 JVM 实时统计监控

【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdk

本文以 JDK 仓库中 jstat 官方手册页 为主体,系统讲解jstat命令的完整参数、全部 11 个统计选项及其列含义、虚拟机标识符(vmid)的构成与远程监控方式,并深入 jdk.jcmd 模块 的源码,剖析这些选项背后由jstat_options描述文件驱动的表格式输出机制。读完本文,你将能够针对本地与远程 JVM 进行 GC、类加载、JIT 编译等维度的实时采样监控,并理解每列数据的来源与输出格式的实现原理。

1. jstat 的定位与前提约束

jstat用于显示受插桩的 Java HotSpot 虚拟机的性能统计信息,目标 JVM 通过虚拟机标识符(vmid)指定。它与 Unix 平台的vmstatiostatmpstatstat系列工具一脉相承——从 Jstat.java 的类注释可以看出,其设计灵感正来源于这套工具。

手册页中有多条重要的前提约束,直接决定使用方式:

  • 实验性命令:手册明确标注 "This command is experimental and unsupported"(该命令是实验性且不受支持的)。
  • 接口不稳定:所有选项及其功能在后续版本中都可能变更或移除。手册特别告诫:不要编写脚本去解析jstat的输出,因为其格式在未来版本中可能变化;如果确实要解析,就要为后续版本做修改准备。
  • 临时目录一致性:如果目标 JVM 以非默认的临时文件位置启动,jstat必须使用相同的临时文件位置才能与其通信——这涉及java命令的-XX:AltTempDir选项。jstat 与目标 JVM 通过本地文件交换统计信息,目录不一致则无法附着。

2. 命令语法总览

手册页给出的标准用法有两种形式:

jstat generalOptions jstat outputOptions [-t] [-h lines] vmid [interval [count]]

参数说明(完整继承自手册页):

参数说明
generalOptions单个通用命令行选项,见下文"通用选项"
outputOptions-options选项报告的一个或多个输出选项,由一个statOption加上可选的-t-h-J选项组成,statOption必须放在最前面
-t在输出的第一列显示时间戳列。时间戳是目标 JVM 启动以来的时间(秒)
-h nn个采样(输出行)显示一次列标题,n为正整数。默认值为0,即只在第一行数据前显示列标题
vmid虚拟机标识符,指示目标 JVM,语法见下文
interval采样间隔,支持s(秒)或ms(毫秒)两种单位,默认单位是毫秒,必须是正整数。指定后jstat按该间隔产生输出
count采样次数。默认值为无穷大,即持续显示统计信息,直到目标 JVM 终止或jstat命令被终止(Ctrl+C)

从 Arguments.java 的toMillis方法可以看到间隔解析实现:只有mss两种合法后缀,缺省按毫秒处理,非法单位会抛出 "Unknown time unit" 异常。因此jstat -gcutil <pid> 250 7中的250表示 250 毫秒间隔;想表达秒级间隔应写1s而非1000(两种写法均可)。

一个手册未直接展开、但源码确认的默认行为值得注意:在 Arguments.java 中,当intervalcount都未给出时,默认值被设为count = 1interval = 0,即只采集一次快照后退出。所以"count 默认为无穷大"成立的前提是你给出了interval而未给出count

2.1 通用选项(General Options)

指定任意一个通用选项后,就不能再指定其他选项或参数:

选项说明
-help显示帮助信息(-?-h--help等效,见 Arguments.java)
-options显示当前平台安装支持的静态选项列表(即所有 statOption)

2.2 输出选项的通用修饰项

如果不指定通用选项,就可以使用输出选项。输出选项决定输出的内容与格式,由单个statOption加上任意其他输出选项(-h-t-J)构成,statOption必须位于最前。

  • 输出格式化为空格分隔的表格,带有标题头行;用-h设置标题显示的频率;
  • 不同选项之间同名列的表头名称保持一致——一般而言,若两个选项提供同名列,则两列的数据源相同;
  • -t选项使输出第一列出现Timestamp列,其值为目标 JVM 启动以来经过的秒数。时间戳的分辨率受多种因素影响,在系统负载较高时可能因线程调度延迟而波动;
  • intervalcount参数分别控制"多久输出一次"和"输出多少次";
  • -J javaOption:把javaOption传递给 Java 应用启动器。例如-J-Xms48m把启动内存设为 48 MB。完整选项列表见java命令的手册页。

3. 虚拟机标识符(vmid)

vmid字符串的语法对应 URI 的语法:

[protocol:][//]lvmid[@hostname[:port][/servername]]

它可以从表示本地 JVM 的简单整数,到指定通信协议、端口号及实现特定值的复杂构造。各组成部分:

组成说明
protocol通信协议。若省略protocol且未指定主机名,则默认为平台相关的优化的本地协议;若省略protocol但指定了主机名,则默认协议为rmi
lvmid目标 JVM 的本地虚拟机标识符,是平台相关的值,用于在系统上唯一标识一个 JVM,是 vmid 中唯一必需的组件。lvmid通常是(但不必然是)目标 JVM 进程的操作系统进程 ID。可以用jps命令确定lvmid(前提是目标 JVM 不在独立的 docker 实例中运行);也可以在 Linux 和 macOS 上用ps命令、在 Windows 上用任务管理器查看
hostname指示目标主机的主机名或 IP 地址。省略时目标为主机自身
port与远程服务器通信的默认端口。若省略hostnameprotocol指定的是优化的本地协议,则port被忽略。对于默认的rmi协议,port 指示远程主机上rmiregistry的端口号;若省略portprotocolrmi,则使用 rmiregistry 的默认端口1099
servername处理方式取决于实现。优化的本地协议下忽略该字段;rmi协议下它表示远程主机上 RMI 远程对象的名称

4. stat 选项与输出列完整参考

以下是手册页中每个 statOption 输出的列含义汇总,共 11 个选项。可用-options通用选项查看特定平台安装实际支持的列表(因为选项集合会随平台与版本变化)。

4.1-class:类加载器统计

含义
Loaded已加载的类数量
Bytes已加载的 KB 数(类占用的字节数)
Unloaded已卸载的类数量
Bytes已卸载的 KB 数(与上一列同名,含义不同,按顺序区分)
Time执行类加载与卸载操作所花费的时间

4.2-compiler:JIT 编译器统计

含义
Compiled已执行的编译任务数
Failed失败的编译任务数
Invalid被失效(invalidate)的编译任务数
Time执行编译任务所花费的时间
FailedType最近一次失败编译的编译类型
FailedMethod最近一次失败编译的类名与方法

4.3-gc:垃圾回收堆统计

含义
S0C当前 survivor 空间 0 容量(KB)
S1C当前 survivor 空间 1 容量(KB)
S0Usurvivor 空间 0 使用量(KB)
S1Usurvivor 空间 1 使用量(KB)
EC当前 eden 空间容量(KB)
EUeden 空间使用量(KB)
OC当前 old 空间容量(KB)
OUold 空间使用量(KB)
MCMetaspace 已提交大小(KB)
MUMetaspace 使用量(KB)
CCSC压缩类空间已提交大小(KB)
CCSU压缩类空间已使用大小(KB)
YGC年轻代 GC 事件数
YGCT年轻代 GC 耗时
FGC完整 GC 事件数
FGCT完整 GC 耗时
GCTGC 总耗时

4.4-gccapacity:各代与空间容量

含义
NGCMN年轻代最小容量(KB)
NGCMX年轻代最大容量(KB)
NGC当前年轻代容量(KB)
S0C当前 survivor 空间 0 容量(KB)
S1C当前 survivor 空间 1 容量(KB)
EC当前 eden 空间容量(KB)
OGCMN老年代最小容量(KB)
OGCMX老年代最大容量(KB)
OGC当前老年代容量(KB)
OC当前 old 空间容量(KB)
MCMNMetaspace 最小容量(KB)
MCMXMetaspace 最大容量(KB)
MCMetaspace 已提交大小(KB)
CCSMN压缩类空间最小容量(KB)
CCSMX压缩类空间最大容量(KB)
CCSC压缩类空间已提交大小(KB)
YGC年轻代 GC 事件数
FGC完整 GC 事件数

4.5-gcutil:GC 统计摘要(百分比视角)

含义
S0survivor 空间 0 使用量占当前容量的百分比
S1survivor 空间 1 使用量占当前容量的百分比
Eeden 空间使用量占当前容量的百分比
Oold 空间使用量占当前容量的百分比
MMetaspace 使用量占当前容量的百分比
CCS压缩类空间使用百分比
YGC年轻代 GC 事件数
YGCT年轻代 GC 耗时
FGC完整 GC 事件数
FGCT完整 GC 耗时
GCTGC 总耗时

4.6-gccause:GC 摘要 + 回收原因

显示与-gcutil相同的 GC 统计摘要,但额外包含最近一次 GC 事件以及(如适用)当前 GC 事件的原因,在-gcutil各列之外增加:

含义
LGCC最近一次 GC 的原因
GCC当前 GC 的原因

4.7-gcnew:年轻代统计

含义
S0C/S1C当前 survivor 空间 0 / 1 容量(KB)
S0U/S1Usurvivor 空间 0 / 1 使用量(KB)
TT当前老化阈值(Tenuring threshold)
MTT最大老化阈值
DSS期望的 survivor 大小(KB)
EC当前 eden 空间容量(KB)
EUeden 空间使用量(KB)
YGC年轻代 GC 事件数
YGCT年轻代 GC 耗时

4.8-gcnewcapacity:年轻代空间大小

含义
NGCMN/NGCMX/NGC年轻代最小 / 最大 / 当前容量(KB)
S0CMX/S0Csurvivor 空间 0 最大 / 当前容量(KB)
S1CMX/S1Csurvivor 空间 1 最大 / 当前容量(KB)
ECMX/ECeden 空间最大 / 当前容量(KB)
YGC年轻代 GC 事件数
FGC完整 GC 事件数

4.9-gcold:老年代与 Metaspace 统计

含义
MCMetaspace 已提交大小(KB)
MUMetaspace 使用量(KB)
CCSC压缩类空间已提交大小(KB)
CCSU压缩类空间已使用大小(KB)
OC当前 old 空间容量(KB)
OUold 空间使用量(KB)
YGC年轻代 GC 事件数
FGC完整 GC 事件数
FGCT完整 GC 耗时
GCTGC 总耗时

4.10-gcoldcapacity:老年代容量

含义
OGCMN/OGCMX/OGC老年代最小 / 最大 / 当前容量(KB)
OC当前 old 空间容量(KB)
YGC/FGC年轻代 / 完整 GC 事件数
FGCT/GCT完整 GC 耗时 / GC 总耗时

4.11-gcmetacapacity:Metaspace 大小

含义
MCMN/MCMXMetaspace 最小 / 最大容量(KB)
MCMetaspace 已提交大小(KB)
CCSMN/CCSMX压缩类空间最小 / 最大容量(KB)
YGC/FGC年轻代 / 完整 GC 事件数
FGCT/GCT完整 GC 耗时 / GC 总耗时

4.12-printcompilation:最近编译方法统计

含义
Compiled最近一次编译方法时的累计编译任务数
Size最近一次编译方法的字节码字节数
Type最近一次编译方法的编译类型
Method标识最近一次编译方法的类名与方法名。类名用斜杠(/)而非点(.)作为命名空间分隔符;两字段的格式与 HotSpot 的-XX:+PrintCompilation选项一致

5. 手册页四个实战示例

以下示例均来自手册页,演示监控一个lvmid为 21891 的本地 JVM。

5.1-gcutil采样:观察一次 Young GC

该示例附着到 lvmid 21891,以 250 毫秒间隔采集 7 个样本:

jstat -gcutil 21891 250 7 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 97.02 70.31 66.80 95.52 89.14 7 0.300 0 0.000 0.300 0.00 97.02 86.23 66.80 95.52 89.14 7 0.300 0 0.000 0.300 0.00 97.02 96.53 66.80 95.52 89.14 7 0.300 0 0.000 0.300 91.03 0.00 1.98 68.19 95.89 91.24 8 0.378 0 0.000 0.378 91.03 0.00 15.82 68.19 95.89 91.24 8 0.378 0 0.000 0.378 91.03 0.00 17.80 68.19 95.89 91.24 8 0.378 0 0.000 0.378 91.03 0.00 17.80 68.19 95.89 91.24 8 0.378 0 0.000 0.378

手册页对该输出的解读:第 3、4 个样本之间发生了一次年轻代收集,耗时 0.078 秒(YGCT 从 0.300 增至 0.378),对象从 eden 空间(E)晋升到 old 空间(O),使 old 空间使用率从 66.80% 上升到 68.19%。收集前 survivor 空间使用率为 97.02%,收集后降为 91.03%(注意 S0/S1 两个 survivor 空间发生了"翻转")。

5.2-h3:重复列标题

该示例以 250 毫秒间隔采样并用-gcnew输出,同时用-h3使列标题每 3 行数据后重新输出一次:

jstat -gcnew -h3 21891 250 S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 64.0 64.0 0.0 31.7 31 31 32.0 512.0 178.6 249 0.203 64.0 64.0 0.0 31.7 31 31 32.0 512.0 355.5 249 0.203 64.0 64.0 35.4 0.0 2 31 32.0 512.0 21.9 250 0.204 S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 64.0 64.0 35.4 0.0 2 31 32.0 512.0 245.9 250 0.204 64.0 64.0 35.4 0.0 2 31 32.0 512.0 421.1 250 0.204 64.0 64.0 0.0 19.0 31 31 32.0 512.0 84.4 251 0.204 S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 64.0 64.0 0.0 19.0 31 31 32.0 512.0 306.7 251 0.204

手册页解读:第 2、3 个样本之间发生了一次年轻代 GC,耗时 0.001 秒(YGCT 从 0.203 增至 0.204)。该次收集发现活跃数据量足够多,若 survivor 空间 0 的使用量(S0U)超过期望 survivor 大小(DSS),于是对象被晋升到老年代(此输出中不可见),老化阈值 TT 从 31 被降到 2。第 5、6 个样本之间又发生一次收集,这次存活对象很少,TT 恢复回 31。这正是-gcnew相比-gcutil的额外价值:能观察到自适应年龄阈值(TT/MTT/DSS)的动态变化。

5.3-t:为每个样本加时间戳

该示例附着到 lvmid 21891,以 250 毫秒间隔采集 3 个样本,-t选项使第一列出现 Timestamp。该列报告目标 JVM 启动以来经过的秒数。同时-gcoldcapacity输出显示 old 代容量(OGC)与 old 空间容量(OC)随堆扩张而增长:第 81 次完整 GC 后,OGC 已从 11,696 KB 增长到 13,820 KB;而该代(与空间)的最大容量为 60,544 KB(OGCMX),仍有扩张余量。

Timestamp OGCMN OGCMX OGC OC YGC FGC FGCT GCT 150.1 1408.0 60544.0 11696.0 11696.0 194 80 2.874 3.799 150.4 1408.0 60544.0 13820.0 13820.0 194 81 2.938 3.863 150.7 1408.0 60544.0 13820.0 13820.0 194 81 2.938 3.863

5.4 远程 JVM 监控

该示例附着到名为remote.domain的系统上 lvmid 40496 的 JVM,使用-gcutil选项,每秒采样一次并无限持续:

jstat -gcutil 40496@remote.domain 1000 ... output omitted

手册页解释:将 lvmid 与远程主机名组合构造出 vmid40496@remote.domain。由于指定了主机名而省略了协议,默认采用rmi协议与远程主机上的默认jstatd服务器通信。jstatd服务器通过remote.domain上绑定到 rmiregistry 默认端口(1099)的rmiregistry命令定位。远程监控的前提是在远端先启动jstatd(由 JDK 仓库中的jdk.jstatd模块提供,见 src/jdk.jstatd)。

6. 源码剖析:stat 选项背后的实现机制

手册页描述的每个 statOption 在实现上都不是硬编码的表格,而是由描述文件 + 解析器 + 表达式求值器驱动的通用框架。这条链路让 jstat 具备了"选项可扩展、列可定义"的结构。

6.1 入口与整体流程

Jstat.java 的main方法流程非常清晰:

  1. new Arguments(args)解析命令行(失败则打印 usage 并退出);
  2. -help→ 打印用法;-options→ 用OptionLister打印所有已定义的选项;
  3. 其余情况分三条路径:logNames()(列出计数器名)、logSnapShot()(快照)、logSamples()(周期采样)。

核心采样路径 logSamples() 的关键步骤:

  • 通过MonitoredHost.getMonitoredHost(vmId)获得监控宿主(本地文件协议或 RMI 协议,正对应手册中 vmid 的协议语义),再getMonitoredVm(vmId, interval)附着到目标 JVM;
  • 若命令携带特殊选项(statOption),构造OptionOutputFormatter(monitoredVm, format);否则按名称模式匹配计数器,走RawOutputFormatter
  • 注册shutdown hook响应用户中断(Ctrl+C),并注册HostListener监听目标 JVM 的终止/断连事件——这正是手册所说"count 默认为无穷大时,直到目标 JVM 终止或 jstat 被终止才停止"的实现;
  • 最后调用logger.logSamples(formatter, headerRate, interval, count, out)执行采样循环,headerRate-h参数控制的标题行重复频率。

6.2 选项定义文件:jstat_options

jstat -gcutil中的列布局定义在资源文件 jstat_options 中,格式是"每列一个column块,含表头、数据表达式、比例、对齐、宽度、格式"。例如timestamp选项:

option timestamp { column { header "^Timestamp" data ((jstat.timestamp - sun.rt.createVmBeginTime) / 1000) scale sec align right width 15 format "0.0" } }

由此可以确认手册页的表述:Timestamp 列就是(当前时间 - 目标 VM 创建时间) / 1000,单位秒(scale sec),宽度 15,保留 1 位小数——与示例输出150.1 / 150.4 / 150.7完全吻合。Timestamp.java 中jstat.timestamp符号的取值就是System.currentTimeMillis()

再看-class-compiler选项的定义(jstat_options 第 37–119 行附近):

  • Loaded=java.cls.loadedClasses + java.cls.sharedLoadedClasses,即同时统计应用类与共享类(如 CDS/元数据共享);
  • Bytes列使用scale K,所以输出是 KB——对应手册中"Number of KB loaded";
  • Time列是sun.cls.time / sun.os.hrt.frequency,即把硬件时钟拍数换算成秒;
  • -compilerCompiled/Failed/Invalid分别映射到sun.ci.totalCompiles / sun.ci.totalBailouts / sun.ci.totalInvalidatesTimejava.ci.totalTime / sun.os.hrt.frequency

这说明手册页中"两个选项提供同名列时数据源相同"的原则在定义文件层面天然成立:列的语义由符号(counter)唯一决定。

Arguments.optionsSources() 揭示了选项文件的加载顺序,这也是手册"-options显示特定平台安装的选项列表"的原因:

  1. ~/.jvmstat/jstat_options(用户目录下的用户自定义/覆盖文件);
  2. jdk.jcmd 模块内置资源sun/tools/jstat/resources/jstat_options
  3. 若设置了-Djstat.showUnsupported,还会追加jstat_unsupported_options

从源码结构看,用户可以在~/.jvmstat/jstat_options中定义自定义选项或覆盖内置列,这是该框架"可定制"的体现;而jstat_unsupported_options则承载了在部分平台上不受支持、默认隐藏的列。

6.3 从解析到输出:OptionFormat 的求值链

  • OptionFinder 依次尝试各 optionsSources,用Parser把文本解析为OptionFormat(列格式对象);当-t开启时,它会把timestamp选项中的列插入为第一列insertSubFormat(0, ...))——这正是-t实现为"在首列插入时间戳列"而非另开输出通道的机制;
  • 若某个 statOption 在上述来源中都找不到定义,Arguments 会抛出Unknown option: -<name>,所以"选项是否可用"完全由选项文件决定,与手册"所有选项可能随版本变更或移除"的告诫一致;
  • OptionOutputFormatter 在构造时通过ExpressionResolver将表达式中的符号解析为对MonitoredVm计数器的实际引用,getHeader()/getRow()分别在HeaderClosure/RowClosure中渲染表头行与数据行——对应输出格式化为空格分隔表格的行为。

6.4 手册未列出的源码级选项

Arguments.java 的帮助信息注释中还列出了一些未公开文档化(undocumented)的选项:-list [<vmid>](列出计数器名)、-snap <vmid>(以 name=value 形式输出计数器快照)、-name <pattern>(输出匹配模式的计数器)、-a/-d(升序/降序排序)、-v-snap的冗长输出)、-constants(连同常量一起输出)、-strings(输出字符串型计数器)。另有-Djstat.debug-Djstat.showUnsupported两个调试开关(见 Arguments.java)。这些属于实现细节,不在手册支持的接口范围内,稳定性不作保证,仅供深入排查时参考。

7. 使用建议小结

结合手册页与源码证据,实践中使用 jstat 的建议:

  1. 选择观察维度:GC 使用率趋势用-gcutil(最常用);要看绝对 KB 值用-gc;要定位 GC 触发原因加-gccause;调年轻代参数时观察-gcnew的 TT/DSS 自适应行为;观察堆扩张用-gcoldcapacity/-gccapacity;诊断类加载器泄漏用-class(关注Unloaded是否停滞);观察 JIT 用-compiler-printcompilation
  2. 控制输出节奏-t加时间戳便于对账,-h <n>在长时间滚动输出时保持可读性;interval缺省按毫秒,跨秒请写10001s;不指定interval时只取一次快照。
  3. 保持临时目录一致:目标 JVM 若使用了AltTempDir,jstat 侧必须一致,否则无法附着。
  4. 远程场景lvmid@hostname形式走 RMI 协议,远端需运行jstatd且 rmiregistry 默认为 1099 端口;容器内运行时jps可能看不到目标,需直接进入容器或从外部用正确的 lvmid 访问。
  5. 不要硬编码解析输出:手册明确输出格式可能变化;如需程序化处理 JVM 统计,应优先考虑 JMX API 等稳定接口,而把 jstat 定位为交互式诊断工具。

8. 参考路径

文件说明
src/jdk.jcmd/share/man/jstat.md本文主体依据的 jstat 手册页源文件
src/jdk.jcmd/share/classes/sun/tools/jstat/Jstat.javajstat 入口与采样主循环
src/jdk.jcmd/share/classes/sun/tools/jstat/Arguments.java命令行解析、interval 单位、默认值、选项文件来源
src/jdk.jcmd/share/classes/sun/tools/jstat/resources/jstat_options全部 stat 选项的列定义文件
src/jdk.jcmd/share/classes/sun/tools/jstat/OptionFinder.java选项解析与 -t 时间戳列注入
src/jdk.jcmd/share/classes/sun/tools/jstat/OptionOutputFormatter.java表头/数据行的格式化输出
src/jdk.jcmd/share/classes/sun/tools/jstat/JStatLogger.java采样循环与日志输出

说明:本文基于 JDK main 分支仓库当前内容编写;jstat为实验性命令,其选项集合与输出列以实际版本中jstat -options的打印结果为准。

【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询