mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
1413 lines
36 KiB
HTML
1413 lines
36 KiB
HTML
<!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 Extensions,Java 管理扩展),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<String, Object> beansMap = loadMXBeanMap();
|
||
|
||
String jsonString = toJSON(beansMap);
|
||
|
||
System.out.println(jsonString);
|
||
|
||
}
|
||
|
||
public static Map<String, Object> 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<MemoryPoolMXBean> memoryPoolMXBeans =
|
||
|
||
ManagementFactory.getMemoryPoolMXBeans();
|
||
|
||
// 3.3 内存管理器-列表
|
||
|
||
List<MemoryManagerMXBean> memoryManagerMXBeans =
|
||
|
||
ManagementFactory.getMemoryManagerMXBeans();
|
||
|
||
// 4. class 加载统计信息
|
||
|
||
ClassLoadingMXBean classLoadingMXBean =
|
||
|
||
ManagementFactory.getClassLoadingMXBean();
|
||
|
||
// 5. 编译统计信息
|
||
|
||
CompilationMXBean compilationMXBean =
|
||
|
||
ManagementFactory.getCompilationMXBean();
|
||
|
||
// 6. 线程
|
||
|
||
ThreadMXBean threadMXBean =
|
||
|
||
ManagementFactory.getThreadMXBean();
|
||
|
||
// 7. GC
|
||
|
||
List<GarbageCollectorMXBean> garbageCollectorMXBeans =
|
||
|
||
ManagementFactory.getGarbageCollectorMXBeans();
|
||
|
||
// 8. 获取平台日志 MXBean
|
||
|
||
PlatformLoggingMXBean platformLoggingMXBean =
|
||
|
||
ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
|
||
|
||
//
|
||
|
||
Map<String, Object> beansMap = new HashMap<String, Object>();
|
||
|
||
//
|
||
|
||
beansMap.put("operatingSystemMXBean", operatingSystemMXBean);
|
||
|
||
beansMap.put("runtimeMXBean", runtimeMXBean);
|
||
|
||
beansMap.put("memoryMXBean", memoryMXBean);
|
||
|
||
beansMap.put("memoryPoolMXBeans", memoryPoolMXBeans);
|
||
|
||
beansMap.put("memoryManagerMXBeans", memoryManagerMXBeans);
|
||
|
||
beansMap.put("classLoadingMXBean", classLoadingMXBean);
|
||
|
||
beansMap.put("compilationMXBean", compilationMXBean);
|
||
|
||
beansMap.put("threadMXBean", threadMXBean);
|
||
|
||
beansMap.put("garbageCollectorMXBeans", garbageCollectorMXBeans);
|
||
|
||
beansMap.put("platformLoggingMXBean", platformLoggingMXBean);
|
||
|
||
return beansMap;
|
||
|
||
}
|
||
|
||
|
||
|
||
public static String toJSON(Object obj) {
|
||
|
||
// MemoryPoolMXBean 这些未设置的属性序列化时会报错
|
||
|
||
SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
|
||
|
||
filter.getExcludes().add("collectionUsageThreshold");
|
||
|
||
filter.getExcludes().add("collectionUsageThresholdCount");
|
||
|
||
filter.getExcludes().add("collectionUsageThresholdExceeded");
|
||
|
||
filter.getExcludes().add("usageThreshold");
|
||
|
||
filter.getExcludes().add("usageThresholdCount");
|
||
|
||
filter.getExcludes().add("usageThresholdExceeded");
|
||
|
||
//
|
||
|
||
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 Control(JMC,大致翻译为 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><IP:端口></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 = "jvm-user";
|
||
|
||
@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 = "TestJMXServer";
|
||
|
||
|
||
|
||
Registry registry = LocateRegistry.createRegistry(rmiPort);
|
||
|
||
server = MBeanServerFactory.createMBeanServer("user");
|
||
|
||
|
||
|
||
ObjectName objectName = new ObjectName("user:name=User");
|
||
|
||
server.registerMBean(bean, objectName);
|
||
|
||
|
||
|
||
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/user");
|
||
|
||
System.out.println("JMXServiceURL: " + 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 = "service:jmx:rmi:///jndi/rmi://localhost:1099/user";
|
||
|
||
JMXServiceURL url = new JMXServiceURL(surl);
|
||
|
||
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
|
||
|
||
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
|
||
|
||
|
||
|
||
System.out.println("Domains:---------------");
|
||
|
||
String domains[] = mbsc.getDomains();
|
||
|
||
for (int i = 0; i < domains.length; i++) {
|
||
|
||
System.out.println("\tDomain[" + i + "] = " + domains[i]);
|
||
|
||
}
|
||
|
||
System.out.println("all ObjectName:---------------");
|
||
|
||
Set<ObjectInstance> set = mbsc.queryMBeans(null, null);
|
||
|
||
for (Iterator<ObjectInstance> it = set.iterator(); it.hasNext();) {
|
||
|
||
ObjectInstance objectInstance = (ObjectInstance) it.next();
|
||
|
||
System.out.println("\t" + objectInstance.getObjectName() + " => " + objectInstance.getClassName());
|
||
|
||
}
|
||
|
||
System.out.println("user:name=User:---------------");
|
||
|
||
ObjectName mbeanName = new ObjectName("user:name=User");
|
||
|
||
MBeanInfo info = mbsc.getMBeanInfo(mbeanName);
|
||
|
||
System.out.println("Class: " + info.getClassName());
|
||
|
||
if (info.getAttributes().length > 0){
|
||
|
||
for(MBeanAttributeInfo m : info.getAttributes())
|
||
|
||
System.out.println("\t ==> Attriber:" + m.getName());
|
||
|
||
}
|
||
|
||
if (info.getOperations().length > 0){
|
||
|
||
for(MBeanOperationInfo m : info.getOperations())
|
||
|
||
System.out.println("\t ==> Operation:" + m.getName());
|
||
|
||
}
|
||
|
||
|
||
|
||
System.out.println("Testing userName and userId .......");
|
||
|
||
Object userNameObj = mbsc.getAttribute(mbeanName,"UserName");
|
||
|
||
System.out.println("\t ==> userName:" + userNameObj);
|
||
|
||
Object userIdObj = mbsc.getAttribute(mbeanName,"UserId");
|
||
|
||
System.out.println("\t ==> userId:" + userIdObj);
|
||
|
||
|
||
|
||
Attribute userNameAttr = new Attribute("UserName","kimmking");
|
||
|
||
mbsc.setAttribute(mbeanName,userNameAttr);
|
||
|
||
|
||
|
||
System.out.println("Modify UserName .......");
|
||
|
||
|
||
|
||
userNameObj = mbsc.getAttribute(mbeanName,"UserName");
|
||
|
||
System.out.println("\t ==> userName:" + 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 => javax.management.MBeanServerDelegate
|
||
|
||
user:name=User => io.github.kimmking.jvmstudy.jmx.User
|
||
|
||
user:name=User:---------------
|
||
|
||
Class: io.github.kimmking.jvmstudy.jmx.User
|
||
|
||
==> Attriber:UserName
|
||
|
||
==> Attriber:UserId
|
||
|
||
Testing userName and userId .......
|
||
|
||
==> userName:jvm-user
|
||
|
||
==> userId:12345678
|
||
|
||
Modify UserName .......
|
||
|
||
==> userName:kimmking
|
||
|
||
|
||
|
||
</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
|
||
|
||
|
||
|
||
{"timestamp":1392110578,"status":200,"request":{"mbean":"org.apache.activemq:brokerName=localhost,type=Broker","attribute":"Queues","type":"read"},"value":[{"objectName":"org.apache.activemq:brokerName=localhost,destinationName=a,destinationType=Queue,type=Broker"}]}
|
||
|
||
|
||
|
||
</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>
|
||
|