无侵入抓取SpringBoot项目JVM指标
🎯 “无侵入地给本地运行的 Spring Boot 项目采集 JVM 指标”
方案:使用 Prometheus JMX Exporter(JavaAgent 注入)
这是唯一真正做到 0 入侵、不修改 Spring Boot 项目的方式。
你只需在启动 Java 程序时追加一个 -javaagent 参数即可。
为什么不用 的 Actuator?
需要:
- 添加依赖
- 修改 application.yml
- 重新打包应用
而 JMX Exporter:
- 不改代码
- 不改 jar
- 不改配置
- 只在启动时增加 JavaAgent
这个方案不会修改你的代码,也不会动 jar 包。
📌 一、下载 JMX Exporter
下载官方 jar(最新版 1.5.0):
jmx_prometheus_javaagent-1.5.0.jar你可以从 GitHub 下载:
https://github.com/prometheus/jmx_exporter/releases
📌 二、创建 JMX Exporter 配置文件(jmx.yaml)
在你的项目目录新建:jmx.yaml

配置文件 jmx.yaml,这个定义 JMX 抓 JVM 相关的所有信息。
startDelaySeconds: 0
ssl: false
# 将导出的 Prometheus label 名(例如 `attr`、`type` 等)全部转换为小写。
lowercaseOutputLabelNames: true
# 把导出的 metric 名(Prometheus 指标名)转换为小写(并把非字母数字字符统一处理)。指标习惯小写并用下划线分隔
lowercaseOutputName: true实战建议 / 常见注意点
- 检查真实的 MBean 域名:用
jconsole或jmxterm看实际 MBean 名(特别是 Tomcat,常见是Catalina而不是Tomcat)。 - 如果抓不到 Tomcat 指标,把
Tomcat:*改为Catalina:*或在白名单加上两者。使用发现内置的 Tomcat 抓不到 - 先用宽松规则调试(把
whitelistObjectNames暂时去掉或放宽),确认关键 MBean 名称和属性,然后再收紧白名单/规则以减少噪声。 - 不要一开始就把所有 MBean 都收集,会产生大量无用指标并增加开销。
- startDelaySeconds:对于慢启动服务,设成
10–30秒通常比较稳妥。 - 生产安全:若服务对外可访问,考虑
ssl: true+ HTTP Basic 或只允许 Prometheus IP 抓取。 - 自定义 metric 名:如果你想更标准或可读的名字,在
rules中可以写更完整的规则(pattern+name:+labels:),从而得到稳定的指标名。
📌 三、启动你的 Spring Boot 项目(无侵入启动)
假设你本地运行项目这样:
java -jar demo.jar现在改成:
java \
-javaagent:D:/learn/jvm/jmx_prometheus_javaagent-1.5.0.jar=12345:D:/learn/jvm/jmx.yaml \
-jar demo.jar这里:
12345→ 暴露给 Prometheus 的端口(你可以改)./jmx.yaml→ 刚才创建的配置文件
启动后,访问:
http://localhost:12345/metrics你会看到:
jvm_memory_bytes_used
jvm_gc_collection_seconds_count
process_cpu_seconds_total
jvm_threads_states_threads
jvm_classes_loaded找到启动配置添加 VM 选项

添加 VM 配置


访问:http://localhost:12345/metrics 展示效果如下

👉 成功!Spring Boot 项目完全无侵入,JVM 指标暴露出来了。
📌 四、Prometheus 抓取配置
在 prometheus.yml 加一个 job:
scrape_configs:
- job_name: 'jvm_demo'
static_configs:
- targets: ['host.docker.internal:12345']如果 Prometheus 也在本地机器运行:
targets:
- ['localhost:12345']📌 五、Grafana 直接导入 JVM Dashboard
推荐以下 Dashboard ID:
⭐ JVM 官方 Dashboard:12900
⭐ 全套 JVM + GC + Thread Dashboard:4701
Grafana → Import → 输入 ID → Done

咦,发现许多指标不存子数据。需要改动定制,请继续看下文。
📌 六、扩展阅读
JMX Exporter 与 JMX 的关系
导入进来显示,但发现有许多指标没有数据,排查原因
如果想要高级定制指标或指定指标
导入进来显示,但发现有许多指标没有数据,排查原因
JMX Exporter pattern 导出来的名跟 Grafana 模版查询字段不一致
JMX Exporter 输出的指标名称(name)不等于 Grafana 官方 JVM Dashboards 期望的指标名称,
所以 Grafana 模板显示为空 / 图表无数据。
1. 为什么 JMX Exporter 的 pattern 导出的名字和 Grafana 不一致?
Grafana 官方 JVM Dashboard(包括社区 JVM Dashboard)通常要求 Prometheus 数据格式类似:
| 指标名称 | 含义 |
|---|---|
jvm_memory_bytes_used | JVM 内存使用量 |
jvm_memory_pool_bytes_used | 各内存池使用量 |
jvm_threads_live | 活动线程数 |
jvm_gc_pause_seconds_count | GC 次数 |
jvm_gc_pause_seconds_sum | GC 暂停时间 |
这些都是 Micrometer(Spring Boot Actuator)生成的名称。
打开你的 exporter 暴露的 /metrics 页面:
http://<host>:9404/metrics暴漏出名称与 Grafana 模板的查询进行对比,你就会发现 完全不一样。
2. 如何让 Grafana 模板正常工作?(两种方法)
自定义 pattern,但必须 “重命名成 Grafana 需要的字段”
如果你坚持用自定义 pattern(比如你想抓 Hikari、自定义 MBean),你必须:
- 调整 name,使其符合 Grafana Dashboard 所需命名
- 或者修改 Grafana Dashboard,让它使用你的字段名
例如 Grafana 需要:
jvm_memory_bytes_used必须手动改成:
name: jvm_memory_bytes_used这样才能匹配。但这种方式非常麻烦,因为 Grafana 需要几十个字段。
高级定制指标或指定指标 (JMX Exporter 配置规则编写)
定制修改指标需要修改配置文件 jmx.yaml 中的 rules 配置。具体如何定制,在了解 Rules 和它的属性后,使用 JConsole 查看 MBean 转换成 pattern 。
什么是 MBean
在这之前需要先知道一个概念 MBean , MBean(Managed Bean)是 JMX 中暴露系统状态的最基础单元。可以把它理解成:
- JVM 内部的一张“监控表”
- 每个 MBean 有一个唯一的名字(ObjectName)
- 每张表有一堆字段(Attributes)
- 有些还提供操作(Operations)
可以通过使用 JConsole / VisualVM / JMC 查看 MBean,下面的示例使用 JConsole 来举例。
介绍 Rules 和它的属性
先来介绍 Rules 和它的属性,了解配置文件的规则。
rules 作用
A list of rules to apply in order, processing stops at the first matching rule. Attributes that aren’t matched aren’t collected. If not specified, defaults to collecting everything in the default format.
要按顺序应用的规则列表,处理会在遇到第一个匹配的规则时停止。未匹配的属性不会被收集。如果未指定,则默认以默认格式收集所有属性。
pattern 有明确的规定
Regex pattern to match against each bean attribute. The pattern is not anchored. Capture groups can be used in other options. Defaults to matching everything.
用于匹配每个 bean 属性的正则表达式模式。该模式未锚定。捕获组可用于其他选项。默认匹配所有内容。可以参考使用说明:https://prometheus.github.io/jmx_exporter/1.5.0/ 其中对 pattern 有明确的规定
我摘出原文:
### Pattern input
The format of the input matches against the pattern is
`domain<beanpropertyName1=beanPropertyValue1, beanpropertyName2=beanPropertyValue2, ...><key1, key2, ...>attrName: value`
|Part |Description|
|--- |---|
|domain |Bean name. This is the part before the colon in the JMX object name.|
|beanPropertyName/Value|Bean properties. These are the key/values after the colon in the JMX object name.|
|keyN |If composite or tabular data is encountered, the name of the attribute is added to this list.|
|attrName |The name of the attribute. For tabular data, this will be the name of the column. If `attrNameSnakeCase` is set, this will be converted to snake case.|
|value |The value of the attribute.|
No escaping or other changes are made to these values, with the exception of if `attrNameSnakeCase` is set. The default help includes this string, except for the value.
### Default format
The default format will transform beans in a way that should produce sane metrics in most cases. It is
`domain_beanPropertyValue1_key1_key2_...keyN_attrName{beanpropertyName2="beanPropertyValue2", ...}: value`
If a given part isn’t set, it’ll be excluded.做了翻译:
### 模式输入
输入的格式与模式匹配
domain<beanpropertyName1=beanPropertyValue1, beanpropertyName2=beanPropertyValue2, ...><key1, key2, ...>attrName: value
|部分 | 描述|
|--- |---|
| domain | Bean名称。这是JMX对象名称中冒号之前的部分
| beanPropertyName/Value| Bean属性。这些是JMX对象名称中冒号后面的键/值|
| keyN | 如果遇到复合或表格数据,则将属性名称添加到该列表中|
|attrName | 属性名。对于表格数据,这将是列的名称。如果设置了‘ attrNameSnakeCase ’,它将被转换为蛇的情况。|
|value | 属性值|
除非设置了‘ attrNameSnakeCase ’,否则不会对这些值进行转义或其他更改。默认的帮助包括这个字符串,除了值。
### 默认格式
默认格式将以一种在大多数情况下应该产生相同指标的方式转换bean。它是
`domain_beanPropertyValue1_key1_key2_...keyN_attrName{beanpropertyName2="beanPropertyValue2", ...}: value`
如果一个给定的部分没有设置,它将被排除在外。示例— 从 JConsole 到 JMX Exporter pattern(直观示例)

原始 MBean 为 java.lang:type=Memory 的 HeapMemoryUsage 属性
其中:
- domain 转换为:
java.lang,直接写,不加引号,不加斜线 - beanPropertyName/Value 转换为
<type=Memory>,多个用逗号隔开<key=value,key2=value2> - keyN 转为为
<>,没有就用尖括号占位置 - attrName 转换为
HeapMemoryUsage:后面有冒号,冒号后有空格 - value 转换为
(.*),这是个正则表达式,取值
拼接下来结果为 -pattern: "java.lang<type=Memory><>HeapMemoryUsage: (.*)",总的来说 pattern 是一个正则表达式 (regex)
示例 二 从 JMX 到 Prometheus 指标(直观示例)
- JMX 属性:
java.lang:type=MemoryPool,name=PS Eden Space,CollectionUsage属性 - 转换后的 Prometheus 指标:
java_lang_memorypool_collectionusage_init{name="PS Eden Space"} 1.33169152E8
(实际名字可以通过lowercaseOutputName、rules规则改)
贴一张图更直观:

JMX Exporter 与 JMX 的关系
JMX 是 Java 内置的管理和监控机制,主要特点:
- 管理 Bean(MBean)
- JVM 内部的各种资源(内存、线程、GC、ClassLoading、操作系统等)都以 MBean 的形式暴露
- 每个 MBean 有:
- ObjectName:唯一标识(如
java.lang:type=Memory) - Attributes(属性):可读/写的数值或对象
- Operations(操作):可执行的方法
- ObjectName:唯一标识(如
- 提供标准接口访问
- 可以通过 JConsole、VisualVM、JMC 等工具直接访问
- 可以远程访问,通过 JMX Remote 或 JMX Connector
- JMX 只是接口和标准
- 它只提供数据,不提供监控/报警/可视化
- JVM、Spring Boot、Tomcat 都会在内部注册 MBean
MX Exporter 是一个桥梁,把 JMX 的 MBean 转成 Prometheus 可抓取的指标。特点:
- 抓取方式
- 通过 JMX Remote 或本地 Agent 访问 JVM 内的 MBean
- 支持两种模式:
- Java Agent(无侵入方式):在启动 JVM 时
-javaagent:jmx_prometheus_javaagent.jar=9404:jmx.yaml - Standalone HTTP Server(侵入式):运行一个 Java 程序连接目标 JVM 的 JMX 端口
- Java Agent(无侵入方式):在启动 JVM 时
- 核心作用
- 读取 MBean 属性值
- 根据 jmx.yaml 规则 pattern 把属性映射成 Prometheus 指标
- 输出
/metricsHTTP 接口,Prometheus 定时抓取
- 为什么需要 JMX Exporter
- JMX 本身只是一套标准,Prometheus 抓不到原生 JMX
- Exporter 做了两件事:
- 连接 JVM 的 JMX
- 将 MBean 的属性转换成 Prometheus 规范指标
JMX Exporter 与 JMX 的关系图(概念)
JVM (Spring Boot / Tomcat / Hikari)
┌─────────────────────────────┐
│ MBeans │
│ ┌─────────────┐ │
│ │ java.lang │ │
│ │ type=Memory │ │
│ │ type=Threading│ │
│ │ ... │ │
│ └─────────────┘ │
└───────────┬─────────────────┘
│ JMX
▼
┌─────────────────────────────┐
│ JMX Exporter │
│ 读取 MBean → 转换成 Prometheus 指标 │
│ 输出 HTTP /metrics │
└───────────┬─────────────────┘
│ Prometheus 抓取
▼
┌─────────────────────────────┐
│ Grafana 可视化 │
└─────────────────────────────┘总结
适合与要求无侵入的项目,适合了解指标数据流转逻辑。单定制指标太麻烦了。要求快速便捷时使用 更快捷和好用。
可以试下
---
wercaseOutputLabelNames: true
lowercaseOutputName: true
whitelistObjectNames: ["java.lang:type=OperatingSystem"]
rules:
- pattern: 'java.lang<type=OperatingSystem><>((?!process_cpu_time)\w+):'
name: os_$1
type: GAUGE
attrNameSnakeCase: true