learn.lianglianglee.com/专栏/JVM 核心技术 32 讲(完)/12 JMX 与相关工具:山高月小,水落石出.md.html
2022-05-11 18:57:05 +08:00

1413 lines
36 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>12 JMX 与相关工具:山高月小,水落石出.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/01 阅读此专栏的正确姿势.md.html">01 阅读此专栏的正确姿势.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/02 环境准备:千里之行,始于足下.md.html">02 环境准备:千里之行,始于足下.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/03 常用性能指标:没有量化,就没有改进.md.html">03 常用性能指标:没有量化,就没有改进.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/04 JVM 基础知识:不积跬步,无以至千里.md.html">04 JVM 基础知识:不积跬步,无以至千里.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/05 Java 字节码技术:不积细流,无以成江河.md.html">05 Java 字节码技术:不积细流,无以成江河.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/06 Java 类加载器:山不辞土,故能成其高.md.html">06 Java 类加载器:山不辞土,故能成其高.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/07 Java 内存模型:海不辞水,故能成其深.md.html">07 Java 内存模型:海不辞水,故能成其深.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/08 JVM 启动参数详解:博观而约取、厚积而薄发.md.html">08 JVM 启动参数详解:博观而约取、厚积而薄发.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/09 JDK 内置命令行工具:工欲善其事,必先利其器.md.html">09 JDK 内置命令行工具:工欲善其事,必先利其器.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/10 JDK 内置图形界面工具:海阔凭鱼跃,天高任鸟飞.md.html">10 JDK 内置图形界面工具:海阔凭鱼跃,天高任鸟飞.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/11 JDWP 简介:十步杀一人,千里不留行.md.html">11 JDWP 简介:十步杀一人,千里不留行.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/JVM 核心技术 32 讲(完)/12 JMX 与相关工具:山高月小,水落石出.md.html">12 JMX 与相关工具:山高月小,水落石出.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/13 常见的 GC 算法GC 的背景与原理).md.html">13 常见的 GC 算法GC 的背景与原理).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/14 常见的 GC 算法ParallelCMSG1.md.html">14 常见的 GC 算法ParallelCMSG1.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/15 Java11 ZGC 和 Java12 Shenandoah 介绍:苟日新、日日新、又日新.md.html">15 Java11 ZGC 和 Java12 Shenandoah 介绍:苟日新、日日新、又日新.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/16 Oracle GraalVM 介绍:会当凌绝顶、一览众山小.md.html">16 Oracle GraalVM 介绍:会当凌绝顶、一览众山小.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/17 GC 日志解读与分析(基础配置).md.html">17 GC 日志解读与分析(基础配置).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/18 GC 日志解读与分析(实例分析上篇).md.html">18 GC 日志解读与分析(实例分析上篇).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/19 GC 日志解读与分析(实例分析中篇).md.html">19 GC 日志解读与分析(实例分析中篇).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/20 GC 日志解读与分析(实例分析下篇).md.html">20 GC 日志解读与分析(实例分析下篇).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/21 GC 日志解读与分析(番外篇可视化工具).md.html">21 GC 日志解读与分析(番外篇可视化工具).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/22 JVM 的线程堆栈等数据分析:操千曲而后晓声、观千剑而后识器.md.html">22 JVM 的线程堆栈等数据分析:操千曲而后晓声、观千剑而后识器.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/23 内存分析与相关工具上篇(内存布局与分析工具).md.html">23 内存分析与相关工具上篇(内存布局与分析工具).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/24 内存分析与相关工具下篇(常见问题分析).md.html">24 内存分析与相关工具下篇(常见问题分析).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/25 FastThread 相关的工具介绍:欲穷千里目,更上一层楼.md.html">25 FastThread 相关的工具介绍:欲穷千里目,更上一层楼.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/26 面临复杂问题时的几个高级工具:它山之石,可以攻玉.md.html">26 面临复杂问题时的几个高级工具:它山之石,可以攻玉.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/27 JVM 问题排查分析上篇(调优经验).md.html">27 JVM 问题排查分析上篇(调优经验).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/28 JVM 问题排查分析下篇(案例实战).md.html">28 JVM 问题排查分析下篇(案例实战).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/29 GC 疑难情况问题排查与分析(上篇).md.html">29 GC 疑难情况问题排查与分析(上篇).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/30 GC 疑难情况问题排查与分析(下篇).md.html">30 GC 疑难情况问题排查与分析(下篇).md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/31 JVM 相关的常见面试问题汇总:运筹策帷帐之中,决胜于千里之外.md.html">31 JVM 相关的常见面试问题汇总:运筹策帷帐之中,决胜于千里之外.md.html</a>
</li>
<li>
<a href="/专栏/JVM 核心技术 32 讲(完)/32 应对容器时代面临的挑战:长风破浪会有时、直挂云帆济沧海.md.html">32 应对容器时代面临的挑战:长风破浪会有时、直挂云帆济沧海.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>12 JMX 与相关工具:山高月小,水落石出</h1>
<p>Java 平台提供了全面的 JVM 监控和管理措施。</p>
<p>在 Java SE 5 之前,虽然 JVM 提供了一些底层的 API比如 JVMPI 和 JVMTI但这些 API 都是面向 C 语言的,需要通过 JNI 等方式才能调用,想要监控 JVM 和系统资源非常不方便。</p>
<p>Java SE 5.0 版本引入了 JMX 技术Java Management ExtensionsJava 管理扩展JMX 技术的前身是“JSR3:Java Management Extensions”以及“JSR 160:JMX Remote API”。</p>
<p>JMX 是用于监控和管理 JVM 资源(包括应用程序、设备、服务和 JVM的一组标准 API。</p>
<p>通过这些 API 接口,可以对外暴露 JVM 和宿主机的一些信息,甚至支持远程动态调整某些运行时参数。</p>
<p>JMX 技术让我们在 JDK 中开发自检程序成为可能,同时也提供了很多轻量级的 API 来监测 JVM 状态和运行中对象/线程状态,从而提高了 Java 语言自身的管理监测能力。</p>
<p>客户端使用 JMX 主要通过两种方式:</p>
<ul>
<li>程序代码手动获取 MXBean</li>
<li>通过网络远程获取 MBean。</li>
</ul>
<p>从 JVM 运行时获取 GC 行为数据,最简单的办法是使用标准 JMX API 接口。JMX 也是获取 JVM 内部运行时状态信息 的标准 API。可以编写程序代码通过 JMX API 来访问本程序所在的 JVM也可以通过 JMX 客户端执行远程访问。MXBean 可用于监控和管理 JVM每个 MXBean 都封装了一部分功能。</p>
<p>如果用通俗的话来讲,就是我们可以在 JVM 这个机构内部搞一个“政务信息公开系统”,这个东西就可以看做是 MBeanServer然后系统默认有很多信息比如 JVM 的基本信息、内存和 GC 的信息等,可以放到这个系统来公开。应用程序里的其他 Java 代码也可以自己定义一些 MBean然后把这些自己想要公开的信息挂到这个系统里来。这个时候就不管是本 JVM 内部,还是其他的 Java 应用程序,都可以访问到这个 MBeanServer 上的所有公开信息,也就是 MBean 的属性,甚至可以直接调用 MBean 提供的方法反过来影响系统。</p>
<h2>获取当前 JVM 的 MXBean 信息</h2>
<p>JDK 默认提供的 MXBean 相关类,主要位于 rt.jar 文件的 java.lang.management 包中。获取 JVM 中 MXBean 信息的代码示例如下:</p>
<pre><code class="language-java">package jvm.chapter11;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.*;
import java.lang.management.*;
import java.util.*;
public class MXBeanTest {
public static void main(String[] args) {
Map&lt;String, Object&gt; beansMap = loadMXBeanMap();
String jsonString = toJSON(beansMap);
System.out.println(jsonString);
}
public static Map&lt;String, Object&gt; loadMXBeanMap() {
// import java.lang.management.*
// 1. 操作系统信息
OperatingSystemMXBean operatingSystemMXBean =
ManagementFactory.getOperatingSystemMXBean();
// 2. 运行时
RuntimeMXBean runtimeMXBean =
ManagementFactory.getRuntimeMXBean();
// 3.1 JVM 内存信息
MemoryMXBean memoryMXBean =
ManagementFactory.getMemoryMXBean();
// 3.2 JVM 内存池-列表
List&lt;MemoryPoolMXBean&gt; memoryPoolMXBeans =
ManagementFactory.getMemoryPoolMXBeans();
// 3.3 内存管理器-列表
List&lt;MemoryManagerMXBean&gt; memoryManagerMXBeans =
ManagementFactory.getMemoryManagerMXBeans();
// 4. class 加载统计信息
ClassLoadingMXBean classLoadingMXBean =
ManagementFactory.getClassLoadingMXBean();
// 5. 编译统计信息
CompilationMXBean compilationMXBean =
ManagementFactory.getCompilationMXBean();
// 6. 线程
ThreadMXBean threadMXBean =
ManagementFactory.getThreadMXBean();
// 7. GC
List&lt;GarbageCollectorMXBean&gt; garbageCollectorMXBeans =
ManagementFactory.getGarbageCollectorMXBeans();
// 8. 获取平台日志 MXBean
PlatformLoggingMXBean platformLoggingMXBean =
ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
//
Map&lt;String, Object&gt; beansMap = new HashMap&lt;String, Object&gt;();
//
beansMap.put(&quot;operatingSystemMXBean&quot;, operatingSystemMXBean);
beansMap.put(&quot;runtimeMXBean&quot;, runtimeMXBean);
beansMap.put(&quot;memoryMXBean&quot;, memoryMXBean);
beansMap.put(&quot;memoryPoolMXBeans&quot;, memoryPoolMXBeans);
beansMap.put(&quot;memoryManagerMXBeans&quot;, memoryManagerMXBeans);
beansMap.put(&quot;classLoadingMXBean&quot;, classLoadingMXBean);
beansMap.put(&quot;compilationMXBean&quot;, compilationMXBean);
beansMap.put(&quot;threadMXBean&quot;, threadMXBean);
beansMap.put(&quot;garbageCollectorMXBeans&quot;, garbageCollectorMXBeans);
beansMap.put(&quot;platformLoggingMXBean&quot;, platformLoggingMXBean);
return beansMap;
}
public static String toJSON(Object obj) {
// MemoryPoolMXBean 这些未设置的属性序列化时会报错
SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
filter.getExcludes().add(&quot;collectionUsageThreshold&quot;);
filter.getExcludes().add(&quot;collectionUsageThresholdCount&quot;);
filter.getExcludes().add(&quot;collectionUsageThresholdExceeded&quot;);
filter.getExcludes().add(&quot;usageThreshold&quot;);
filter.getExcludes().add(&quot;usageThresholdCount&quot;);
filter.getExcludes().add(&quot;usageThresholdExceeded&quot;);
//
String jsonString = JSON.toJSONString(obj, filter,
SerializerFeature.PrettyFormat);
return jsonString;
}
}
</code></pre>
<p>取得这些 MXBean 之后,就能采集到对应的 Java 运行时信息,定时上报给某个系统,那么一个简单的监控就创建了。</p>
<p>当然,这么简单的事情,肯定有现成的轮子啦。比如 Spring Boot Actuator以及后面介绍的 Micrometer 等。各种监控服务提供的 Agent-lib 中也会通过类似的手段采集相应的数据。</p>
<p>如果想通过编程方式获取远程机器上的 MXBean请参考</p>
<blockquote>
<p><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/management/mxbeans.html">Using the Platform MBean Server and Platform MXBeans</a></p>
</blockquote>
<h2>使用 JMX 工具远程连接</h2>
<p>最常见的 JMX 客户端是 JConsole 和 JVisualVM可以安装各种插件十分强大。两个工具都是标准 JDK 的一部分,而且很容易使用. 如果使用的是 JDK 7u40 及更高版本还可以使用另一个工具Java Mission ControlJMC大致翻译为 Java 控制中心)。</p>
<p>监控本地 JVM 并不需要额外配置,如果是远程监控,还可以在服务端部署 Jstatd 服务暴露部分信息给 JMX 客户端。</p>
<p>所有 JMX 客户端都是独立的程序,可以连接到目标 JVM 上。目标 JVM 可以在本机,也可以是远端 JVM。</p>
<p>想要支持 JMX 客户端连接服务端 JVM 实例,则 Java 启动脚本中需要加上相关的配置参数,示例如下:</p>
<pre><code class="language-shell">-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=10990
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
</code></pre>
<p>如果服务器具有多张网卡(多个 IP由于安全限制必须明确指定 hostname 一般是 IP。</p>
<pre><code class="language-shell">-Djava.rmi.server.hostname=47.57.227.67
</code></pre>
<p>这样启动之后JMX 客户端(如 JConsole、JVisualVM、JMC就可以通过 <code>&lt;IP:端口&gt;</code> 连接。(参考 JVisualVM 的示例)。</p>
<p>如这里对应的就类似于47.57.227.67:10990。</p>
<blockquote>
<p>如果想要远程查看 VisualGC则服务端需要开启 Jstatd 来支持JVisualVM 先连 Jstatd 远程主机,接着在远程主机上点右键添加 JMX 连接。关于 JVisualVM 的使用请参考前面的文章《JDK 内置图形界面工具》。</p>
</blockquote>
<p>以 JConsole 为例,我们看一下,连接到了远程 JVM 以后,在最后一个面板即可看到 MBean 信息。</p>
<p>例如,我们可以查看 JVM 的一些信息:</p>
<p><img src="assets/58f59680-2d68-11ea-a743-8137f2e46c91" alt="enter image description here" /></p>
<p>也可以直接调用方法,例如查看 VM 参数:</p>
<p><img src="assets/629d8300-2d68-11ea-a743-8137f2e46c91" alt="enter image description here" /></p>
<p>如果启动的进程是 Tomcat 或者是 Spring Boot 启动的嵌入式 Tomcat那么我们还可以看到很多 Tomcat 的信息:</p>
<p><img src="assets/6bd52180-2d68-11ea-b8bc-595e31068820" alt="enter image description here" /></p>
<h2>JMX 的 MBean 创建和远程访问</h2>
<p>前面讲了在同一个 JVM 里获取 MBean现在我们再来写一个更完整的例子创建一个 MBean然后远程访问它。</p>
<p>先定义一个 UserMBean 接口(必须以 MBean 作为后缀):</p>
<pre><code class="language-java">package io.github.kimmking.jvmstudy.jmx;
public interface UserMBean {
Long getUserId();
String getUserName();
void setUserId(Long userId);
void setUserName(String userName);
}
</code></pre>
<p>然后实现它:</p>
<pre><code class="language-java">package io.github.kimmking.jvmstudy.jmx;
public class User implements UserMBean {
Long userId = 12345678L;
String userName = &quot;jvm-user&quot;;
@Override
public Long getUserId() {
return userId;
}
@Override
public String getUserName() {
return userName;
}
@Override
public void setUserId(Long userId) {
this.userId = userId;
}
@Override
public void setUserName(String userName) {
this.userName = userName;
}
}
</code></pre>
<p>最后实现一个类来启动 MBeanServer</p>
<pre><code class="language-java">package io.github.kimmking.jvmstudy.jmx;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class UserJmxServer {
public static void main(String[] args){
MBeanServer server;
User bean=new User();
try {
int rmiPort = 1099;
String jmxServerName = &quot;TestJMXServer&quot;;
Registry registry = LocateRegistry.createRegistry(rmiPort);
server = MBeanServerFactory.createMBeanServer(&quot;user&quot;);
ObjectName objectName = new ObjectName(&quot;user:name=User&quot;);
server.registerMBean(bean, objectName);
JMXServiceURL url = new JMXServiceURL(&quot;service:jmx:rmi:///jndi/rmi://localhost:1099/user&quot;);
System.out.println(&quot;JMXServiceURL: &quot; + url.toString());
JMXConnectorServer jmxConnServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
jmxConnServer.start();
}catch (Exception e){
e.printStackTrace();
}
}
}
</code></pre>
<p>通过这几个代码我们可以看到,使用 MBean 机制,需要:</p>
<ul>
<li>先定义 MBean 接口;</li>
<li>实现这个接口;</li>
<li>然后把接口和类,注册到 MBeanServer这里可以用 JVM 里的默认 MBeanServer也可以自己创建一个新的 Server这里为了简单就使用了默认的。</li>
</ul>
<p>然后我们就可以使用客户端工具或者代码来访问 MBeanServer查看和操作 MBean由于 MBean 类似反射的机制(如果早期做过 Windows 平台的 COM 对象开发,就会发现是类似的),客户端不需要知道具体的 MBean 接口或者实现类,也能请求服务器端。</p>
<p>如果大家学习过 Apache Dubbo就知道在 Dubbo 里消费端必须拿到服务提供者的服务接口,才能配置和调用,这里不同的地方就是客户端是不需要 MBean 接口的。</p>
<h3>JConsole 里查看自定义 MBean</h3>
<p>首先我们启动这个应用 UserJmxServer接下来我们使用工具来查看和操作它。</p>
<p>打开 JConsole在远程输入</p>
<pre><code>service:jmx:rmi:///jndi/rmi://localhost:1099/user
</code></pre>
<p><img src="assets/7c66ebf0-2d68-11ea-b6a8-fbb27a899f8c" alt="enter image description here" /></p>
<p>查看 User 的属性:</p>
<p><img src="assets/84cfa1b0-2d68-11ea-8fee-9f496e85c50f" alt="enter image description here" /></p>
<p>直接修改 UserName 的值:</p>
<p><img src="assets/8cb2acb0-2d68-11ea-b6fe-19eaa05aac2e" alt="enter image description here" /></p>
<h3>使用 JMX 远程访问 MBean</h3>
<p>我们先使用 JMXUrl 来创建一个 MBeanServerConnection连接到 MBeanServer然后就可以通过 ObjectName也可以看做是 MBean 的地址,像反射一样去拿服务器端 MBean 里的属性,或者调用 MBean 的方法。示例如下:</p>
<pre><code class="language-java">package io.github.kimmking.jvmstudy.jmx;
import javax.management.*;
import javax.management.remote.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Iterator;
import java.util.Set;
public class UserJmxClient {
public static void main(String[] args){
try {
String surl = &quot;service:jmx:rmi:///jndi/rmi://localhost:1099/user&quot;;
JMXServiceURL url = new JMXServiceURL(surl);
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
System.out.println(&quot;Domains:---------------&quot;);
String domains[] = mbsc.getDomains();
for (int i = 0; i &lt; domains.length; i++) {
System.out.println(&quot;\tDomain[&quot; + i + &quot;] = &quot; + domains[i]);
}
System.out.println(&quot;all ObjectName---------------&quot;);
Set&lt;ObjectInstance&gt; set = mbsc.queryMBeans(null, null);
for (Iterator&lt;ObjectInstance&gt; it = set.iterator(); it.hasNext();) {
ObjectInstance objectInstance = (ObjectInstance) it.next();
System.out.println(&quot;\t&quot; + objectInstance.getObjectName() + &quot; =&gt; &quot; + objectInstance.getClassName());
}
System.out.println(&quot;user:name=User---------------&quot;);
ObjectName mbeanName = new ObjectName(&quot;user:name=User&quot;);
MBeanInfo info = mbsc.getMBeanInfo(mbeanName);
System.out.println(&quot;Class: &quot; + info.getClassName());
if (info.getAttributes().length &gt; 0){
for(MBeanAttributeInfo m : info.getAttributes())
System.out.println(&quot;\t ==&gt; Attriber&quot; + m.getName());
}
if (info.getOperations().length &gt; 0){
for(MBeanOperationInfo m : info.getOperations())
System.out.println(&quot;\t ==&gt; Operation&quot; + m.getName());
}
System.out.println(&quot;Testing userName and userId .......&quot;);
Object userNameObj = mbsc.getAttribute(mbeanName,&quot;UserName&quot;);
System.out.println(&quot;\t ==&gt; userName&quot; + userNameObj);
Object userIdObj = mbsc.getAttribute(mbeanName,&quot;UserId&quot;);
System.out.println(&quot;\t ==&gt; userId&quot; + userIdObj);
Attribute userNameAttr = new Attribute(&quot;UserName&quot;,&quot;kimmking&quot;);
mbsc.setAttribute(mbeanName,userNameAttr);
System.out.println(&quot;Modify UserName .......&quot;);
userNameObj = mbsc.getAttribute(mbeanName,&quot;UserName&quot;);
System.out.println(&quot;\t ==&gt; userName&quot; + userNameObj);
jmxc.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
</code></pre>
<p>直接运行,输出如下:</p>
<pre><code>Domains:---------------
Domain[0] = JMImplementation
Domain[1] = user
all ObjectName---------------
JMImplementation:type=MBeanServerDelegate =&gt; javax.management.MBeanServerDelegate
user:name=User =&gt; io.github.kimmking.jvmstudy.jmx.User
user:name=User---------------
Class: io.github.kimmking.jvmstudy.jmx.User
==&gt; AttriberUserName
==&gt; AttriberUserId
Testing userName and userId .......
==&gt; userNamejvm-user
==&gt; userId12345678
Modify UserName .......
==&gt; userNamekimmking
</code></pre>
<p>在前面的 JConsole 示例中,我们可以看到 JMX 的 MBeanServer 里的所有 MBean 就是一个树结构,那么怎么定位一个 MBean 对象就是靠它的地址ObjectName 属性,例如例子里的 <code>user:name=User</code>。ObjectName 跟 LDAP 里定位的 DN 非常像,可以直接在客户端拿到一个服务端实际对象的代理对象。然后进行操作:</p>
<ul>
<li>queryMBeans查询当前 Server 的所有 MBean 对象,进而可以拿到每个 MBean 内的 MBeanInfo 信息,有什么属性和方法。</li>
<li>getAttribute从 Server 上拿到某个 MBean 对象的某个属性值。</li>
<li>setAttribute设置 Server 上的某个 MBean 的某个属性值。</li>
<li>invoke调用 Server 上某个 MBean 的某个方法。</li>
</ul>
<p>从上面的分析我们可以看到JMX 其实是基于 MBean 和 MBeanServer 模型、RMI 协议,在设计上非常精巧的远程调用技术。通过学习这个技术的细节,我们可以了解一般的 RPC 等技术。学会了这种 JVM 默认的管理 API 技术,我们也可以更方便的了解和分析 JVM 情况。</p>
<h2>更多用法</h2>
<p>JMX 是基于 RMI 的 JVM 管理技术,底层是 Java 平台专有的 RMI 远程方法调用,很难做到跨语言调用。怎么才能做到跨平台呢?现在最火的远程调用方式非 REST 莫属。能否让 JMX 使用 REST API 直接调用呢?答案是肯定的。</p>
<p>另外,想要进行性能分析,只有 JVM 的信息还是不够的,我们还需要跟其他的各类监控集成,比如 Datadog 或是其他 APM本篇只是简单涉及。</p>
<h3>JMX 与 REST API</h3>
<p>先说一下 JMX 的 REST API有一个框架 Jolokia它可以自动把 JMX 的结构转换成 REST API 和 JSON 数据。在开源软件 ActiveMQ 的控制台里就默认使用了这个框架,这样可以直接达到如下效果。</p>
<p>我们使用 curl 手工执行一次 REST 调用,会直接返回给我们 API 的 JSON 结果。</p>
<pre><code class="language-shell">$ curl http://localhost:8161/hawtio/jolokia/read/org.apache.activemq:brokerName=localhost,type=Broker/Queues
{&quot;timestamp&quot;:1392110578,&quot;status&quot;:200,&quot;request&quot;:{&quot;mbean&quot;:&quot;org.apache.activemq:brokerName=localhost,type=Broker&quot;,&quot;attribute&quot;:&quot;Queues&quot;,&quot;type&quot;:&quot;read&quot;},&quot;value&quot;:[{&quot;objectName&quot;:&quot;org.apache.activemq:brokerName=localhost,destinationName=a,destinationType=Queue,type=Broker&quot;}]}
</code></pre>
<p>更多信息,可以阅读参考材料。</p>
<h3>JMX 与其他软件</h3>
<p>JConsole 及 JVisualVM 等工具提供了实时查看的能力,但如果我们想监控大量 JVM 实例的历史数据,应该怎么办呢?</p>
<p>既然 JMX 提供了这些数据,只要我们有一个工具来定时采集,并上报给对应的 APM 收集系统,那么我们就保存了长期的历史数据,作为进一步分析和性能诊断的依据。</p>
<p>例如 DataDog听云等服务提供商都集成了对 JMX 的支持。</p>
<p>因为我们的专栏主要讲解 JDK 相关工具的用法所以想了解的同学请搜索关键字“Datadog JMX”或者“听云 JMX”等等。</p>
<blockquote>
<p>如果你搜索“Spring JMX“甚至能看到 JMX 可以把很多东西玩出花来,但 JMX 比起 HTTP API 来说还是比较重的所以对于具有编程能力的企业和工程师来说想要灵活和方便的话HTTP 接口才是最方便的方式。</p>
</blockquote>
<h2>相关链接</h2>
<ul>
<li><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/index.html">Java Management Extensions (JMX) Guide</a></li>
<li><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/management/index.html">Monitoring and Management for the Java Platform</a></li>
<li><a href="https://blog.csdn.net/kimmking/article/details/9170563">在 ActiveMQ 中使用 JMX</a></li>
<li><a href="https://jolokia.org/">Jolokia is remote JMX with JSON over HTTP.</a></li>
<li><a href="https://blog.csdn.net/KimmKing/article/details/19081973">ActiveMQ REST Management API</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/JVM 核心技术 32 讲(完)/11 JDWP 简介:十步杀一人,千里不留行.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/JVM 核心技术 32 讲(完)/13 常见的 GC 算法GC 的背景与原理).md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996fd8a8de3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>