一、为什么 2025 年了还值得聊 JMX?

如果你是写 Java 的,大概率对 JMX 的印象还停留在几件事上:

  • 早年在服务器上开个 JMX 端口,用 jconsole / VisualVM 连上去看内存、线程、GC 曲线;

  • 偶尔看到某个连接池文档里说一句:enable JMX metrics

  • 线上排障时,监控同学说:“Kafka / Tomcat 的指标是通过 JMX 抓的。”

表面上看,JMX 像是一个 “老掉牙的远程监控接口”,跟今天动不动就是 Prometheus、OpenTelemetry、Grafana 的云原生世界,好像没什么关系。

但只要你往下追一层,会发现:

几乎所有 JVM 生态里的“高级组件”,都把自己的内部状态挂在了 JMX 上。

例如:

  • JDK 自带的内存、线程、GC、ClassLoading 指标,全是 MXBean;

  • Tomcat、Jetty、各种 Java EE 服务器,通过 JMX 暴露线程池、连接器、会话数;

  • Kafka、ActiveMQ、Cassandra、HBase 等中间件,通过 JMX 暴露吞吐、延迟、队列长度;

  • 各种连接池(HikariCP、Druid)、缓存(Ehcache)、任务调度(Quartz)…… 也都习惯用 JMX 暴露“内部体征”。

而云原生监控体系里干的事,其实是:

  • 在 Pod / JVM 旁边放一个 JMX Exporter / APM Agent

  • 这些 Agent 去连 JMX,把 MBean 里的属性读出来;

  • 再转成 Prometheus 指标 / OpenTelemetry 数据,上报给 Grafana / APM 后台。

所以在 2025 年回头看 JMX,有两个现实理由:

1)它依然是 JVM 世界的“官方管理 & 监控总线”。

哪怕你永远不直接写 JMX 代码,你也在间接依赖它——因为你用的各种框架、组件都在用。

2)理解 JMX,可以帮你把“JVM 内部”与“云原生监控外部世界”连成一条线。

当你搞不明白 Prometheus / APM 面板上的某个 JVM 指标是怎么来的,追到最后,大概率会落到某个 MBean / MXBean 上。

这篇文章的目的,不是再给你背一遍官方定义,而是:

从“JVM 自带管理总线”的角度,把 JMX 放进你的 JVM 心智模型里,并顺手连到云原生监控世界。

二、JMX 到底是什么?JVM 自带的一条“管理总线”

我们先把所有复杂名词收一收,用一张“脑内总线图”来理解 JMX:

有一堆“可以被管理的对象”(线程池、连接池、缓存、JVM 自己),

它们通过统一的接口挂在一条总线上,

任何管理/监控工具只要接上这条总线,就能读状态、改参数、触发操作。

这条总线,就是 JMX。

在 JMX 里,几个核心概念绕不过去:

1. MBean:被管理的对象(Managed Bean)

MBean(Managed Bean)可以直接理解为:

“我要暴露给外部管理的那一块对象视图。”

比如你有一个线程池管理器:

  • 它内部有:
    • 核心线程数、最大线程数、当前队列长度;

    • 调整核心线程数、拒绝策略等操作。

  • 你就可以给它配一个 MBean,让外部管理工具可以:
    • 读这些属性(Attribute);

    • 调这些管理操作(Operation)。

JMX 里会把这些“要暴露出去的属性和操作”描述成一份元数据:MBeanInfo 对应的实现体,则是一个 DynamicMBean 或由它包出来的 Standard MBean / MXBean / Model MBean。

2. MBeanServer:统一注册中心和调用总线

MBean 再多,如果没有一个统一的“路由中心”,没人知道去哪找谁。

这个角色就是 MBeanServer,可以简单理解为:

“MBean 的注册表 + 统一入口 + 调度总线。”

它负责几件事:

  • 注册 / 注销 MBean:registerMBean(object, objectName)

  • ObjectName 查找 MBean;

  • 对外提供统一的方法:
    • getAttribute(ObjectName, "AttributeName")

    • setAttribute(ObjectName, Attribute)

    • invoke(ObjectName, "operationName", Object[] params, String[] signature)

所有远程管理请求,最终都会落到某个 MBeanServer 上,再由它转发到具体 MBean。

3. Connector / Adapter:把总线接到外部世界

光有 JVM 里的总线还不够,外面的工具要能连进来:

  • 最经典的是 JMX Remote API + RMI: 启一个 JMX RMI 端口,jconsole / jvisualvm / JMC 就能通过这个端口连上来。

  • 现代云原生里更常见的是各种 适配器 / Exporter / Agent
    • Prometheus JMX Exporter:JMX → /metrics HTTP → Prometheus;

    • Jolokia:JMX → HTTP + JSON;

    • APM Agent:JMX → 内部协议 → APM 后台。

它们的共同点都是:

从外部接入 JMX 总线,读写 MBean 暴露的那些属性/操作。

三、从一行代码看源码:Platform MBeanServer 是怎么长出来的?

很多教程一上来就是这一句:

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

我们从这行代码往下看一眼,JVM 里到底发生了什么(用“源码视角”的抽象版来讲,不贴长代码)。

1. ManagementFactory:给你一个“平台级” MBeanServer

ManagementFactory.getPlatformMBeanServer() 做的事情大致是:

1)检查 JVM 里是否已经创建过“平台 MBeanServer”;

2)如果没有,就:

创建一个默认实现:JmxMBeanServer

把 JVM 自带的一堆 平台 MXBean 注册进去:

  • 内存:MemoryMXBean

  • 线程:ThreadMXBean

  • GC:GarbageCollectorMXBean

  • ClassLoading、Runtime、OperatingSystem……等等。

从使用者角度,你只看到一句简单调用,但实际上:

你拿到的是一条已经挂满“JVM 自己管理接口”的总线, 你可以继续往上挂你自己的 MBean。

2. JmxMBeanServer:MBeanServer 的默认实现

在 OpenJDK 里,JmxMBeanServerMBeanServer 的一个标准实现。

它内部有两件很关键的东西:

1)一个 MBeanServerDelegate

  • 描述这个 MBeanServer 自己的信息(版本、厂商等);

  • 负责发一些“服务器级”的通知(比如某个 MBean 被注册/注销)。

2)一个 MBeanServerInterceptor(默认实现是 DefaultMBeanServerInterceptor):

  • 真正干活的家伙:所有 registerMBean / getAttribute / invoke 最终都会由它处理。

可以粗暴地画成:

ManagementFactory
    ↓ 创建
JmxMBeanServer
    ↓ 组合
[MBeanServerDelegate] + [DefaultMBeanServerInterceptor]

你调用:

mbs.registerMBean(myBean, objectName);
mbs.getAttribute(objectName, "Attr");
mbs.invoke(objectName, "doSomething", ...);

其实 JmxMBeanServer 只是个门面,真正逻辑都被丢给了 Interceptor。

3. Interceptor 里发生了什么:一切 MBean 最终都是 DynamicMBean

DefaultMBeanServerInterceptor 里,大概会做几步检查和转换:

1)当你注册一个对象时:

如果这个对象本身就是 DynamicMBean,那就直接用;

如果是 Standard MBean / MXBean,JMX 会:

  • 找到对应的接口(XXXMBean / XXXMXBean);

  • 用反射做一轮 内省(introspection)

  • 把它包装成一个内部的 DynamicMBean 实现;

  • 同时生成一份 MBeanInfo,描述属性/操作/通知。

简单说:

不管你写的是哪种风格的 MBean,

最终在 MBeanServer 眼里都是一个 DynamicMBean + 一份 MBeanInfo

2)当你调用 getAttribute / invoke 时:

  • Interceptor 通过 ObjectName 在内部 Repository 里找到对应的 DynamicMBean

  • 调用它的 getAttribute(name) / invoke(name, params, signature)

  • DynamicMBean 再通过反射,去调用你真实对象上的 getter / 方法。

这条链路一旦理解,你就能把各种 MBean 类型(Standard / MXBean / Model / Open MBean)串到同一条路线上:

上层写法各有风格,底层全都收敛到 DynamicMBean 的动态调用模型上。

到这里,我们已经把三个关键问题打通了:

  1. 为什么 2025 年还要聊 JMX:因为它依旧是 JVM 世界的“官方管理总线”,只是今天更多是被 Exporter / Agent 利用,而不是让人肉连 jconsole。

  2. JMX 到底是什么:一套围绕 MBean/MBeanServer/Connector 建起来的“被管理对象模型 + 总线 + 远程访问接口”。

  3. Platform MBeanServer 是怎么来的ManagementFactory.getPlatformMBeanServer() 背后,会创建一个 JmxMBeanServer,并自动挂上 JVM 自带的各种 MXBean。

四、四种 MBean 形态:Standard / MXBean / Open MBean / Model MBean

JMX 世界里“叫 MBean 的东西”很多:Standard MBean、MXBean、Open MBean、Model MBean……

名字一多,最容易迷糊的就是:它们到底有什么共同点,又是怎么不一样的?

先记一个总纲:

不管哪一派,最后在 MBeanServer 眼里,都被统一成:

DynamicMBean + 一份 MBeanInfo

上层只是“写法、数据模型、可配置程度”不同而已。

4.1 Standard MBean:靠命名规范 + 接口自动内省

这是最经典、最适合“入门理解”的那种 MBean。

写法长这样:

public interface HelloMBean {
    String getMessage();
    void setMessage(String message);
    void sayHello();
}

public class Hello implements HelloMBean {
    private String message;

    @Override
    public String getMessage() { return message; }

    @Override
    public void setMessage(String message) { this.message = message; }

    @Override
    public void sayHello() {
        System.out.println("Hello " + message);
    }
}

注册:

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("demo:type=Hello");
mbs.registerMBean(new Hello(), name);

JMX 干的事:

  • 找到 HelloMBean 这个接口(名字以 MBean 结尾);

  • 扫描接口方法:
    • getMessage()/setMessage() → 属性 message

    • sayHello() → Operation;

  • 生成一份 MBeanInfo

  • Hello 包装成内部的 DynamicMBean 实现。

适用场景:

  • 管理接口相对稳定;

  • 主要给 Java 工具(jconsole、VisualVM、JMC)用;

  • 想要“写代码 → 编译 → 注册”这一条比较直接的路径。

4.2 MXBean:让数据模型对“非 Java 客户端”更友好

Standard MBean 有个问题:

参数 / 返回值可以是任意 Java 类型,

远程访问时走 RMI + Java 序列化,

客户端必须有同样的类。

这对通用监控工具、跨语言客户端很不友好。

于是 JMX 定义了一套 OpenType / OpenData 模型,对外统一用一种“开放结构”描述数据。

MXBean 就是:在接口层保持“正常 Java 写法”,由 JDK 自动帮你做 Java 类型 ↔ OpenType 的映射。

特点:

  • 接口名字以 MXBean 结尾,或者加 @MXBean

  • 方法参数 / 返回值类型必须是 可映射到 OpenType 的类型
    • 基本类型、包装类型、String;

    • 枚举;

    • 简单 Java Bean(通过 getter 组合成结构体);

    • List<T>Map<String, T> 等。

JDK 会在内部做两件事:

  1. MXBeanIntrospector 内省接口,生成 MBeanInfo

  2. MXBeanMapping 系列类,把 Java 对象转成 CompositeData / TabularData 等 OpenData。

这就是为什么 JVM 自带的那些 MemoryMXBeanThreadMXBeanGarbageCollectorMXBean

  • 看起来用的都是普通 Java 类型(比如 MemoryUsage);

  • jconsole / Prometheus JMX Exporter 却能在完全不知道这些类定义的情况下,把数据“读”出来。

一句话:

Standard MBean = 更偏 Java 世界内部;

MXBean = 为“跨进程、跨语言、通用监控工具”设计的数据接口。

4.3 Open MBean:直接手撸 OpenType / OpenData

MXBean 是“把普通 Java 类型自动映射到 OpenType”;

Open MBean 则是:你自己直接操作 OpenType / OpenData。

核心类型都在 javax.management.openmbean 包里:

类型描述(schema):

  • SimpleType:简单值(Integer、String…)

  • CompositeType:结构体(字段名 → 类型)

  • TabularType:表(很多行,每行是 CompositeType)

  • ArrayType:数组

数据实例:

  • CompositeData / CompositeDataSupport

  • TabularData / TabularDataSupport

你可以写一个 MBean,所有方法都用这些 OpenData 类型作为参数和返回值。 对远程客户端来说,它看到的是:

  • “有 schema 的强类型结构体/表格”,而不是某个你自定义的 Java class。

现实里:

  • 直接手撸 Open MBean 的机会不多;

  • 大部分时候你看到的是“MXBean + OpenType”这条组合拳。

4.4 Model MBean:元数据驱动的“通用 MBean 引擎”

Standard / MXBean / Open MBean 有一个共同特点:

“一类 MBean = 一段 Java 代码”。

换句话说,每多一个被管理的“资源”,你都要多写一个类/接口。

JMX 规范还设计了一种更“理想主义”的机制:Model MBean

核心思想:

  • 有一个通用实现类:RequiredModelMBean

  • 你用 ModelMBeanInfo + Descriptor 的方式,把:
    • 这个 MBean 有哪些属性/操作/通知;

    • 它们对应到被管理对象的哪些方法;

    • 缓存、持久化、记录日志等策略; 全都描述在一份“模型”里。

运行时的流程:

  1. 创建 RequiredModelMBean

  2. setManagedResource(target, "ObjectReference"),告诉它要管理哪个对象;

  3. setModelMBeanInfo(info),把“模型信息”塞进去;

  4. 注册到 MBeanServer

之后所有 getAttribute / invoke 调用,都是 RequiredModelMBean 根据 ModelMBeanInfo + Descriptor 来转发的。

实际使用感受:

  • 对框架作者、想做“超通用可配置管理平台”的人很友好;

  • 对普通业务开发者来说:门槛高 + 心智成本大,远不如 Standard/MXBean 那么顺手

  • 在现代 Spring / 云原生生态下,大多数场景已经被“简单 MBean + HTTP/指标”覆盖了。

4.5 四种主要形态怎么选?

可以用一个“拍脑袋决策树”来记:

  • 只想暴露一点简单属性/操作给 jconsole / 内部工具用? → 写 Standard MBean 就够了。

  • 希望监控系统、不同语言的客户端都能看懂这份数据? → 用 MXBean(自动映射到 OpenType)。

  • 已经在显式操作 CompositeData / TabularData,想完全掌控 OpenType? → 直接用 Open MBean / OpenData。

  • 你是框架作者,想做一个“完全靠元数据驱动的通用 MBean 引擎”? → 可以研究 Model MBean / RequiredModelMBean。

但不管哪一种,记住那条总线就不会迷路:

统统都会被包成 DynamicMBean,挂到同一个 MBeanServer 上。

五、JMX vs Spring Bean:两个“Bean 世界”的交集与差异

JMX 叫 MBean,Spring 叫 bean,看起来好像一家人。 但实际上,它们关注的是两个完全不同的维度。

5.1 设计目标:管理 vs 装配

JMX 的 MBean 关心的是:

  • “这个对象对运维/监控要暴露哪些属性和操作?”

  • “外面的管理工具要怎么远程读写状态、触发动作?”

典型关键词:

  • 属性(Attribute)

  • 操作(Operation)

  • 通知(Notification)

  • 远程调用、RMI、管理控制台、监控工具

Spring 的 bean 关心的是:

  • “这个对象的实例由谁 new?依赖怎么注入?生命周期怎么管理?”

  • “Controller 调 Service,Service 调 Repository,这一大串引用怎么自动装起来?”

典型关键词:

  • IoC(控制反转)、DI(依赖注入)

  • BeanFactory / ApplicationContext

  • @Autowired、构造器注入

  • AOP、事务代理、生命周期回调

一句话对比:

MBean = 运维 / 管理视角的对象模型

Spring Bean = 应用内部依赖装配视角的对象模型

5.2 底层机制:都是“容器 + 元数据 + 反射”,但用法完全不同

两边都有类似的“结构”:

JMX:

  • 容器:MBeanServerObjectNameDynamicMBean

  • 元数据:MBeanInfo(属性/操作/通知描述)

  • 调用方式:getAttribute(...) / invoke(...) → 字符串 + 参数数组 → 反射

Spring:

  • 容器:BeanFactory / ApplicationContext(beanName → Bean 实例)

  • 元数据:BeanDefinition(class、scope、构造器参数、属性注入等)

  • 调用方式:getBean("xxx") → 拿到真实对象 / 代理 → 普通方法调用

所以从实现手法看,确实有点“同宗”:

  • 都有注册表;

  • 都有元数据;

  • 都用反射。

但落地路线是两条完全不同的路:

  • JMX:面向远程管理调用,API 形态就是:

Object getAttribute(ObjectName name, String attribute);
Object invoke(ObjectName name, String operationName, Object[] params, String[] signature);
  • Spring:面向应用内部业务调用,对上层只暴露:

Object getBean(String name);

拿到对象之后,业务方法就是普通的 Java 调用(或者加了 AOP 代理)。

5.3 两个世界的“交叉点”:Spring 把 bean 暴露成 MBean

真正把这两个世界串起来的,是 Spring 的 JMX 支持:MBeanExporter

它干的是一件“桥接”性质的事:

  1. 从 Spring 视角:
    • 有一些已经被 IoC 容器管理的 bean;

    • 想把其中一部分对外变成“可管理的 MBean”。

  2. MBeanExporter 做的事情:
    • 扫描带 @ManagedResource / @ManagedAttribute / @ManagedOperation 的 bean;

    • 或者根据配置的 ObjectName → beanName 映射;

    • 选用 Standard / Model MBean 策略;

    • 注册到 MBeanServer 里。

  3. 对 JMX 视角来说:
    • 它只看见新的 MBean 被注册了;

    • 完全不知道背后是 Spring Bean 还是手写对象。

于是我们就有了这样的链路:

Spring Bean(业务世界)

→ MBeanExporter

→ MBean / MBeanServer(管理总线)

→ JConsole / Exporter / APM(监控 & 管理世界)

这也顺手证明了一件事:

JMX MBean 和 Spring Bean 是两套独立机制,

只是 Spring 写了一座桥,让应用内部对象也能挂到 JMX 总线上。

六、JMX 在云原生时代的新角色:从 jconsole 到 Prometheus / APM

在单体 / 裸机 / 早期虚机时代,JMX 最常见的使用方式是这样的:

  1. 在应用启动参数里配置一堆 -Dcom.sun.management.jmxremote.*

  2. 线上机器开放一个 JMX RMI 端口;

  3. 运维工程师用 jconsole / jvisualvm / JMC 连过去;

  4. 看堆 / 线程 / GC / 自定义 MBean,顺便在线改个参数。

但在 Kubernetes / Service Mesh / 零信任安全时代,这种玩法几乎宣告死亡:

  • Pod IP 会变,没法像以前那样“记住一个机器名 + 端口”;

  • RMI 协议多端口、穿透性差,对 Ingress / ServiceMesh 极度不友好;

  • 安全治理不允许随便在外网上暴露一堆能够远程执行代码的 JMX 端口。

于是 JMX 的使用姿势发生了一个很大的“视角翻转”:

从“人连过去改东西” → 变成“Agent / Exporter 连过去采数据”。

6.1 典型链路一:JMX → Prometheus → Grafana

Java 服务 + 云原生监控里,最常见的是这一条:

JVM / Tomcat / Kafka / HikariCP / Ehcache ...
    ↓(以 MBean 形式暴露内部状态)
JMX(MBeanServer)
    ↓(Pod 内 / 同 JVM 内)
JMX Exporter / Java Agent
    ↓(HTTP /metrics)
Prometheus
    ↓
Grafana / 报警系统

落在实战里会长这样:

  • 把 Prometheus JMX Exporter 以 Java Agent 的形式挂到 JVM 起动参数上;

  • 它通过本地 JMX 直接读所有 MBean 的属性;

  • 把这些数据映射成 Prometheus 指标(jvm_memory_used_bytestomcat_threads_busykafka_server_BrokerTopicMetrics…);

  • 以 HTTP /metrics 暴露出来,Prometheus 定期 scrape;

  • Grafana 看图、Prometheus 触发告警。

人几乎不会直接动手连 JMX,而是:

  • 在 DashBoard 上看指标;

  • 知道这些指标 behind-the-scenes 是从哪些 MBean 里读出来的。

6.2 典型链路二:JMX → APM Agent → 可观测性平台

各家 APM / Observability 平台(Datadog、New Relic、Elastic APM、SkyWalking 等)也会把 JMX 当成一个重要数据源。

链路类似:

JVM / 中间件 / 连接池 / 缓存 ...
    ↓(MBean 暴露内部状态)
JMX
    ↓
APM Agent(Java)
    ↓(自定义协议 / OTLP / HTTP)
APM / Observability 后台(Metrics + Traces + Logs)

Agent 会做几件事:

  • 从 JMX 拉取 JVM / 容器 / 中间件的状态;

  • 从字节码增强点上收集调用链、异常信息;

  • 整合之后发给后端。

对你来说,JMX 变成:

“APM Agent 跟 JVM 打交道时的一条官方通道”。

6.3 云原生排障中的 JMX:最后一公里的“直连内脏”能力

虽然大部分时候我们只看仪表盘,但在疑难杂症场景下,JMX 仍然提供了一个“直连 JVM 内脏”的兜底能力。

常见做法:

  • 在 K8s 集群里,对单个 Pod 做 kubectl port-forward

  • 临时暴露一个只在本机可见的 JMX 端口;

  • 用 jconsole / jvisualvm / JMC 连进去,看:
    • 某时刻的堆分布;

    • 线程状态(是否有死锁、阻塞);

    • 某些自定义 MBean 的实时状态(队列长度、失败次数、开关状态);

  • 必要时通过 MBean 临时:
    • 改点参数(比如某个限流阈值、日志级别);

    • 手动触发一次诊断操作(dump、重置统计等)。

这条路今天用得不算频繁,但在“线上事故排查 / 高危操作”场景下,依然是那根可以伸进去捏一捏心脏的纤维镜

  • HTTP 接口没暴露这些能力;

  • 指标也只告诉你“病了”,但 JMX 能让你看到更细节的内部状态。

6.4 在现代项目里,怎么“正确看待” JMX?

可以给自己定一个比较现实、工程化的定位:

1)JMX 是 JVM 世界里的“管理总线”,不是最终界面。

  • 直接连 JMX 的人会越来越少;

  • 消费它的主要是 Exporter / Agent / Actuator。

2)指标 / Dashboard 优先走 Prometheus / OTel;JMX 做数据源。

  • 大部分新监控方案都不建议“直接挂 JMX 到监控系统”;

  • 而是推荐通过 Exporter 或 APM Agent 把它转换成更统一的指标 / 事件模型。

3)安全上,不要随便对外开放 JMX 端口。

  • 只在 Pod 内使用(Java Agent、Sidecar);

  • 或临时调试时通过 port-forward,且加认证 / 网络限制。

4)如果你要自定义 MBean,记得它是“长期在线 + 被监控频繁访问”的对象。

  • 不要在 getter 里写重逻辑(避免监控反过来拖垮业务);

  • 谨慎设计操作的“威力”(确保不会被误触发导致灾难性后果)。

七、谁在大量用 JMX?一张「JMX 生态雷达图」

聊到这里,很多人会有个直觉问题:

“听起来 JMX 这么底层,那现在到底还有谁在认真用它?”

如果你把自己平时接触到的 JVM 生态组件扫一遍,会发现一张很清晰的 「JMX 生态雷达图」

1. JVM 自己:JMX 是“亲儿子”

第一圈就是 JDK 自带的 JVM 管理接口

  • MemoryMXBean:堆/非堆、已用、提交、最大值;

  • ThreadMXBean:线程数量、守护线程数、死锁检测;

  • GarbageCollectorMXBean:各个 GC 的次数、耗时;

  • 还有 ClassLoadingMXBeanRuntimeMXBeanOperatingSystemMXBean 等。

所有这些都在你执行:

ManagementFactory.getPlatformMBeanServer();

那一刻,被注册到平台 MBeanServer 上。

也就是说,即使你什么都没写,JVM 自己已经是一条挂满 MBean 的“总线”了

2. Web 容器:Tomcat / Jetty / Java EE 服务器

第二圈是各种 Java Web 容器和应用服务器:

Tomcat

  • 线程池状态(maxThreads、currentThreadsBusy、currentThreadCount 等);

  • Connector(HTTP/HTTPS/AJP)的请求数、错误数、吞吐;

  • Session 数量、会话超时等。

Jetty / Undertow / 各类 Java EE 容器(WebLogic, WebSphere, WildFly…):

  • 都有自己的 JMX MBean 集合,用来管理线程池、数据源、部署应用等。

很多文章里看到那种 “监控 Tomcat 线程池” 的做法,本质就是连上 JMX,把这些容器内置的 MBean 属性读出来。

3. 中间件:Kafka / ActiveMQ / Cassandra / HBase / Hadoop…

第三圈是你在分布式系统里经常遇到的一票中间件:

  • Kafka:Broker、Topic、Controller、网络层、请求处理层…全部通过 JMX 暴露度量;

  • ActiveMQ / Artemis / RocketMQ:队列长度、消费者数量、消息堆积、DLQ 情况;

  • Cassandra / HBase / Hadoop 组件:节点负载、请求延迟、Region/Tablet 状态等,也有大量 JMX 指标。

如果你看过 Prometheus JMX Exporter 的官方例子,就会发现它的核心“示例目标”基本都是这些家伙。

4. 基础组件:连接池 / 缓存 / 定时任务

往内再缩一圈,是你日常写业务时直接会用到的基础库:

数据库连接池

  • HikariCP:活动连接数、空闲连接数、最大连接数、等待时间分布;

  • Druid / DBCP / C3P0:连接池状态、SQL 统计、慢查询数量等。

缓存

  • Ehcache、Hazelcast、Infinispan 等:缓存命中率、大小、失效次数。

任务调度

  • Quartz 等:Job/Trigger 的状态、下次执行时间、失败次数。

在很多 Spring Boot 项目里,你只要开一下配置,比如 spring.datasource.hikari.register-mbeans=true

这些东西就会乖乖挂到你的 MBeanServer 下面,等待被监控系统“发现”。

5. 监控工具 & 网关:站在 JMX 的“另一端”

还有一圈是 专门为 JMX 存在的工具

JConsole / VisualVM / Java Mission Control

  • 这些是“直连” JMX 的图形客户端,用得越来越少,但在高危排障时仍然很好用。

Jolokia

  • 一个把 JMX 映射成 HTTP + JSON 的代理;

  • 很多老项目的“通过 HTTP 看 JVM 状态”的方案,其实就是嵌了个 Jolokia。

Prometheus JMX Exporter、各种 APM Agent

  • 统一套路就是:JMX → 本地采集 → 转成 metrics/traces → 发给 Prometheus / OpenTelemetry / APM 后台。

所以,即使你的代码里一行 JMX 都没写,你的项目也极有可能已经深度“泡”在 JMX 生态里了。

6. Dubbo / Spring Boot / 微服务世界

最后再提一句跟微服务绑定很紧的两块:

Spring Boot Actuator

  • Actuator 提供的健康状态、指标、环境信息,除了 HTTP 端点,也可以通过 JMX 暴露;

  • 只要你开了 spring.jmx.enabled=true,就能在 MBeanServer 里看到一堆 org.springframework.boot:type=Endpoint 开头的 MBean。

Dubbo

  • Dubbo 核心现在更偏 Metrics + Prometheus / OTel;

  • 但在 Dubbo + Spring Boot 的场景下,它的健康信息会被塞进 Actuator,间接通过 JMX 暴露出来

从这个角度看:

JMX 更像是 JVM 生态“中间的一层基建”,

上面是各种框架、微服务、监控体系,

下面是 JVM 本身和一堆中间件组件。

八、一个从源码到云原生的最小 Demo:HelloMBean → JConsole → JMX Exporter

讲了这么多概念,我们用一个完整但尽量简洁的 Demo,把这条链路打通:

自己写一个 HelloMBean → 用 JConsole 连上去玩一玩 → 再用 JMX Exporter 把它变成 Prometheus 指标。

你可以按下面步骤实操一遍,体感会非常清晰。

8.1 第一步:写一个最简单的 Standard MBean

先定义一个 MBean 接口 + 实现类:

// HelloMBean.java
public interface HelloMBean {
    String getMessage();
    void setMessage(String message);

    int getCounter();
    void resetCounter();
    void sayHello();
}
// Hello.java
public class Hello implements HelloMBean {

    private String message = "World";
    private int counter = 0;

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public int getCounter() {
        return counter;
    }

    @Override
    public void resetCounter() {
        counter = 0;
    }

    @Override
    public void sayHello() {
        counter++;
        System.out.println("Hello " + message + " #" + counter);
    }
}

注意两点:

  • 接口名必须是 HelloMBean(Standard MBean 命名规范);

  • 实现类叫 Hello,实现了这个接口。

8.2 第二步:注册到 Platform MBeanServer

写一个 main 方法,把它注册到平台 MBeanServer:

import javax.management.*;
import java.lang.management.ManagementFactory;

public class JmxDemoApp {
    public static void main(String[] args) throws Exception {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

        ObjectName name = new ObjectName("demo:type=Hello,name=Hello1");
        Hello mbean = new Hello();

        mbs.registerMBean(mbean, name);

        System.out.println("HelloMBean registered. Press Enter to exit.");
        System.in.read();
    }
}

跑起来之后,这个 Java 进程就会:

  • 有一个 ObjectName = "demo:type=Hello,name=Hello1" 的 MBean;

  • 挂在平台 MBeanServer 上;

  • JVM 自带的内存、线程、GC MXBean 也都在同一个 MBeanServer 里。

8.3 第三步:用 JConsole 连上去玩一玩

如果你在本机跑这个 Demo,最简单的方式:

1)直接运行 JmxDemoApp

2)在命令行执行:jconsole

3)在弹出来的列表中选择你的 Demo 进程(JConsole 会列出当前用户下的所有本地 Java 进程);

4)连接之后,点到「MBeans」这一栏,展开:demoHelloHello1

你会看到:

  • Attributes:MessageCounter

  • Operations:sayHello()resetCounter()

你可以:

  • 在 Attributes 里修改 Message 的值;

  • 在 Operations 里点 sayHello(),看控制台输出;

  • 多点几次,再刷新 Counter,看计数器是否递增。

这一步,让你对“MBean = 被管理对象视图”有了非常直观的感受。

8.4 第四步:加一层 JMX Exporter,变成 Prometheus 指标

JConsole 只是“人对 MBean”,接下来我们让“监控系统对 MBean”。

使用 Prometheus JMX Exporter 有两种常见方式:

  1. 作为 Java Agent 挂在 JVM 上(推荐);

  2. 作为一个独立进程,连远程 JMX(不太云原生)。

这里用 Java Agent 方式演示思路(配置略写,文章里可以再贴完整 YAML):

8.4.1 准备 jmx_exporter Java Agent

假设你已经下载了 jmx_prometheus_javaagent.jar,放在某个目录下。

再写一个最简单的配置 config.yml,比如只抓我们的 demo:type=Hello,*

startDelaySeconds: 0
jmxUrl: ""
ssl: false
lowercaseOutputName: true
lowercaseOutputLabelNames: true

rules:
  - pattern: 'demo<type=Hello,name=Hello1><>(Counter|Message):'
    name: 'hello_${1}'
    type: GAUGE

这段配置的意思是:

  • 匹配 demo:type=Hello,name=Hello1 这个 MBean;

  • 把它的 Counter / Message 属性映射成指标:hello_counter, hello_message(只是示意)。

实战里你可以做更多映射,比如给 Counter 打上 label、或者只暴露 Counter

8.4.2 以 Java Agent 启动 Demo

在启动参数里加上一句类似:

java \
  -javaagent:/path/to/jmx_prometheus_javaagent.jar=12345:/path/to/config.yml \
  -cp target/classes \
  JmxDemoApp

含义是:

  • 把 JMX Exporter 作为一个 javaagent;

  • 在本地开一个端口 12345,提供 /metrics HTTP 接口;

  • 它会从当前 JVM 的 本地 MBeanServer 里抓取所有 MBean(包括你的 HelloMBean 和 JVM 自带的 MXBean)。

8.5 第五步:Prometheus + 浏览器验证

接下来你可以:

1)在浏览器打开:http://localhost:12345/metrics

  • 你会看到一堆 jvm_ 开头的指标;

  • 如果配置正确,也能看到类似 hello_counter 这样的指标。

2)在本地起一个 Prometheus,把 localhost:12345 配成一个 scrape target:

  • Scrape 后,在 Prometheus UI 中查询 hello_counter

  • 每点一次 JConsole 里的 sayHello(),看 hello_counter 是否往上跳。

至此,你就用一条完整的链路把 JMX 玩了一遍:

Hello(Java 对象)
  ↓(实现 HelloMBean 接口)
HelloMBean(Standard MBean)
  ↓(registerMBean)
MBeanServer(挂上所有 MBean,包括 JVM 自带的 MXBean)
  ↓
JConsole(人类管理)
  ↓
JMX Exporter / Agent(Prometheus 视角)
  ↓
/metrics HTTP
  ↓
Prometheus / Grafana(云原生监控视角)

这条链路一旦在你脑子里“亮起来”,很多以前模糊的问题都会变得很清晰,比如:

  • “为什么 Kafka 监控要配 JMX 端口 + JMX Exporter?”

  • “为什么 HikariCP / Tomcat 的指标可以直接被 Prometheus 抓到?”

  • “JVM 自带的 GC/内存指标到底是从哪儿来的?”

答案几乎都是:

它先挂在 JMX 的 MBeanServer 上,

再被某个 Exporter / Agent 抓出来,

最后送进 Prometheus / APM / 可观测性平台。

九、实战建议:在现代 Spring Boot / K8s 项目里,如何“正确使用” JMX?

说了这么久 JMX,最后落到工程实践上,问题其实只有一个:

在一个典型的 Spring Boot + Kubernetes 项目里,

JMX 应该“在什么地方被认真对待”,在什么地方“当作透明的底层基建”?

我给你几条可以直接带走的「实战建议」。

9.1 优先用 Prometheus / Micrometer 做指标,JMX 当“数据源”

在今天的云原生栈里:

  • 指标采集:Prometheus / OTEL Metrics

  • 仪表盘:Grafana / APM 控制台

  • Spring Boot 指标出口:Micrometer + Actuator

这套组合已经成为事实标准。所以在设计监控方案时:

优先考虑:

  • 业务指标:通过 MeterRegistry 暴露(@Timedcounter(), gauge() 等);

  • 系统指标:JVM / Tomcat / HikariCP / Kafka 等,交给 JMX Exporter / APM Agent 去采;

  • 对外统一:用 /actuator/prometheus 或 Prometheus JMX Exporter。

JMX 的角色是:

  • 不直接面对人类;

  • 作为 “JVM 内部 → 监控系统” 的中间桥梁和数据源

你可以把它想象成:

“Prometheus 在天上飞,Micrometer 在中间跑,JMX 在地上趴。”

只要你知道它在下面趴着,就可以放心地把指标体系交给上面两层。

9.2 什么时候值得自己写 MBean?

不是所有东西都值得“专门搞个 MBean”,但有一些场景非常适合:

1)长命且关键的资源管理器

比如你自己实现了:

  • 自研线程池(不是直接用 JDK 的 ThreadPoolExecutor);

  • 分布式任务调度器;

  • 消费者/生产者管理器(封装了多条队列、多种重试策略);

  • 跨服务的本地缓存 / 限流器管理中心。

这些东西有几个共性:

  • 生命周期贯穿整个服务运行;

  • 内部状态复杂,并且「管理视角」很重要(多少任务在跑、多少在排队、失败数多少);

  • 需要在不重启的情况下做一些调试 / 调参。

这类组件非常适合:

  • 写一个 Standard MBean / MXBean;

  • 把关键状态(队列长度、失败次数、开关状态、限流阈值)暴露为属性;

  • 把一些管理操作暴露成 Operation(清理缓存、重置统计、执行一次 Check 等)。

2)需要“临时调参”的功能开关

比如:

  • 某个回源策略的开关;

  • 某条异步任务的最大并发数;

  • 日志级别、调试模式开关。

这些开关你也可以放在配置中心里,但:

  • 配置中心更新 → 下发 → 推荐做灰度,流程重;

  • 某些排障场景,你希望有一个“只对这一个实例生效的临时修改”通道。

这时候 MBean 很适合作为「医生私下调试用的旋钮」:

  • 正式方案还是走配置中心;

  • JMX 只是留一条“紧急通道”。

9.3 在 Spring Boot 里如何优雅地暴露 MBean?

如果你已经是 Spring Boot 项目,推荐直接利用 Spring 自带的 JMX 支持,而不是自己去调底层 API。

1)开启 JMX 支持(如果未开启)

在配置里:

spring:
 jmx:
   enabled: true

这样 Spring 会启动一个 MBeanExporter,自动把符合规则的 bean 暴露到 JMX。

2)使用注解声明管理接口

你可以在某个 Spring Bean 上加 JMX 注解(需要引入 spring-context 的 JMX 支持):

@ManagedResource(objectName = "demo:type=MyManager")
public class MyManager {

   private int threshold = 10;

   @ManagedAttribute
   public int getThreshold() {
       return threshold;
   }

   @ManagedAttribute
   public void setThreshold(int threshold) {
       this.threshold = threshold;
   }

   @ManagedOperation
   public void reset() {
       // ...
   }
}

这样:

  • Spring 会帮你构造合适的 MBean(通常走 Standard / Model MBean 路线);

  • 把它注册到 MBeanServer;

  • JConsole / JMX Exporter / APM Agent 就能看到它。

3)配合 Actuator 做统一管理入口

  • 对外管理入口:优先用 Actuator HTTP 端点(/actuator/health/actuator/metrics/actuator/env 等);

  • 在某些需要更深层调试的场景,可以用 JMX 作为“后门”

9.4 K8s 环境下要注意的几件“小事”

在 Kubernetes 环境里使用 JMX,有几个很现实的注意点:

1)尽量不要暴露 JMX RMI 端口给外网

  • JMX 本身支持远程代码执行,安全面太大;

  • 多端口 + RMI 特性也和云原生网络模型不太合。

建议:

  • 只在 Pod 内部使用 JMX(给 Agent/Exporter 用);

  • 或通过 kubectl port-forward 做临时调试,且仅限可信网络。

2)JMX Exporter 尽量用 Java Agent 模式

  • 优先选择 -javaagent 模式绑定到 JVM;

  • 避免在容器里开额外的 RMI 端口给 Exporter 去连;

  • 减少网络和配置复杂度。

3)MBean 的 getter 不要做重逻辑

  • 监控系统会频繁 getAttribute

  • 如果你在 getter 里做了复杂计算 / IO 操作,很容易反过来拖垮服务。

实战建议:

  • Getter 只读已有状态,最多做 O(1) 或轻量计算;

  • 重计算可以异步做,把结果缓存到字段里,再通过 Getter 暴露。

4)谨慎暴露“危险操作”

  • 不要在 MBean Operation 里放“清库”“停服务”之类的东西;

  • 必要的管理操作也要做好幂等和风险控制(参数校验、限流、防抖)。

9.5 一句“工程实践的口头禅”

“指标交给 Prometheus,管理交给 Actuator,

但是别忘了 JMX 是你在 JVM 里最后一层可以动手的总线。”

十、总结:把 JMX 放进你的 JVM 心智模型

最后,我们把文章前面铺设的所有点,压缩成一个尽量简洁、但足够立体的心智模型。

10.1 一句话版:JMX 是什么?

JMX = JVM 的“管理总线”。

  • MBean:挂在总线上的“被管理对象视图”;

  • MBeanServer:总线的注册中心与调度核心;

  • Connector / Exporter / Agent:总线和外部世界之间的桥。

10.2 与其他关键词的相对位置

在你的 Java / 云原生技术栈脑图里,可以这么定位:

  • Servlet / Spring MVC / WebFlux:处理请求的“流量通道”;

  • JDBC / ORM / 连接池:处理数据访问的“存储通道”;

  • 配置体系(Environment + 配置中心 + ConfigMap):控制行为的“参数通道”;

  • 日志体系(SLF4J + Logback / Log4j2 + ELK / Loki):记录行为的“叙述通道”;

  • 指标 / Trace / 日志聚合(Prometheus / OTEL / APM):对外暴露可观测性的“观测通道”;

  • JMX:在 JVM 内部,把“这些组件的状态和操作挂在一条总线上的管理通道”。

也就是说:

当你看到某个组件说“支持 JMX”时,你应该自动联想到:

“哦,它的是把内部状态、操作挂到了这条管理总线上,

后面的 Exporter / APM / JConsole 可以顺着这条线去看它、控它。”

10.3 重新理解那句老话:“别再只会 jconsole 了”

以前我们提 JMX,脑海里浮现的是:

  • JDK 自带的 jconsole;

  • 一堆连到远程 JVM 的图形界面。

今天我们再提 JMX,更应该想到的是:

  • JVM 里那堆 xxxMXBean 指标的来源;

  • Tomcat / Kafka / HikariCP / Ehcache 等组件的“管理快照”;

  • Prometheus JMX Exporter / APM Agent 背后连着的那条总线。

所以,“别再只会 jconsole 了”的真正含义是:

不要把 JMX 仅仅看成一个 GUI 工具的协议,

而要把它当成 JVM 内部管理 & 监控的统一抽象层。

10.4 一句提示

下次你在项目里遇到这些关键词:

  • “这个指标是怎么来的?”

  • “这个连接池的内部状态能不能看到?”

  • “Kafka 这几个 metrics 和内置的 MBean 是什么关系?”

  • “能不能临时调一下这个组件的参数,不重启就生效?”

不妨先在脑子里问自己三件事:

  1. 这个东西有没有 MBean / MXBean?

  2. 这些 MBean 挂在 MBeanServer 上了没有?名字大概是什么?

  3. 是谁在消费这些 MBean:人、脚本、Actuator,还是 Exporter / APM Agent?

如果你能把这三件事想清楚,JMX 就再也不是一个“古早名词”,

而是你理解 JVM、组件、云原生监控之间关系的 一根非常清晰的主线

更多推荐