使用 JMX 監(jiān)控和管理 Java 程序
此篇文章介紹 Java JMX 技術(shù)的相關(guān)概念和具體的使用方式。

當(dāng)前文章屬于Java 性能分析優(yōu)化系列文章,點(diǎn)擊話題可以查看所有文章。
1. JMX 是什么?
Java Management Extensions(JMX)技術(shù)是 Java SE 平臺(tái)的標(biāo)準(zhǔn)功能,提供了一種簡單的、標(biāo)準(zhǔn)的監(jiān)控和管理資源的方式,對于如何定義一個(gè)資源給出了明確的結(jié)構(gòu)和設(shè)計(jì)模式,主要用于監(jiān)控和管理 Java 應(yīng)用程序運(yùn)行狀態(tài)、設(shè)備和資源信息、Java 虛擬機(jī)運(yùn)行情況等信息。JMX 是可以動(dòng)態(tài)的,所以也可以在資源創(chuàng)建、安裝、實(shí)現(xiàn)時(shí)進(jìn)行動(dòng)態(tài)監(jiān)控和管理,JDK 自帶的 jconsole 就是使用 JMX 技術(shù)實(shí)現(xiàn)的監(jiān)控工具。
使用 JMX 技術(shù)時(shí),通過定義一個(gè)被稱為 MBean 或 MXBean 的 Java 對象來表示要管理指定的資源,然后可以把資源信息注冊到 MBean Server 對外提供服務(wù)。MBean Server 充當(dāng)了對外提供服務(wù)和對內(nèi)管理 MBean 資源的代理功能,如此優(yōu)雅的設(shè)計(jì)讓 MBean 資源管理和 MBean Server 代理完全獨(dú)立開,使之可以自由的控制 MBean 資源信息。
JMX 不僅僅用于本地管理,JMX Remote API 為 JMX 添加了遠(yuǎn)程功能,使之可以通過網(wǎng)絡(luò)遠(yuǎn)程監(jiān)視和管理應(yīng)用程序。
2. 為什么使用 JMX 技術(shù)?
JMX 技術(shù)為 Java 開發(fā)者提供了一種簡單、靈活、標(biāo)準(zhǔn)的方式來監(jiān)測 Java 應(yīng)用程序,得益于相對獨(dú)立的架構(gòu)設(shè)計(jì),使 JMX 可以平滑的集成到各種監(jiān)控系統(tǒng)之中。
下面列舉幾項(xiàng) JMX 的具體優(yōu)點(diǎn):
開箱即用的監(jiān)控功能,JMX 是 Java SE 的標(biāo)準(zhǔn)部分,提供了資源管理、服務(wù)托管、遠(yuǎn)程監(jiān)控等管理基礎(chǔ)功能,都可以直接啟用。 JMX 技術(shù)提供了一種通用的、標(biāo)準(zhǔn)的資源、系統(tǒng)、應(yīng)用程序、網(wǎng)絡(luò)的管理方式,不僅可以本地使用、遠(yuǎn)程使用;還可以擴(kuò)展到其他場景,如 Java EE 應(yīng)用等。 JMX 技術(shù)提供了對 JVM 狀態(tài)的監(jiān)測功能,JMX 已經(jīng)內(nèi)置了對 JVM 的監(jiān)測功能,并且可以監(jiān)控和管理 JVM,十分方便。 JMX 架構(gòu)設(shè)計(jì)優(yōu)秀,組件化的設(shè)計(jì)可以自由的擴(kuò)展。 JMX 技術(shù)嚴(yán)格遵守 Java 現(xiàn)有規(guī)范如 JNDI 規(guī)范。 JMX 可以自由的與其他管理解決方案集成,得益于開放的 JMX API,可以通過 web 服務(wù)管理 JMX 中的資源。
3. JMX 的技術(shù)架構(gòu)
JMX 技術(shù)架構(gòu)主要有資源管理(MBean/MXBean)模塊,資源代理模塊(MBean Server),遠(yuǎn)程管理模塊(Remote API)組成 ,下面的圖片來自維基百科,很好的展示了三個(gè)模塊之間的關(guān)系。

3.1. 資源管理 MBean
資源管理在架構(gòu)中標(biāo)識(shí)為資源探測層(Probe Level),在 JMX 中, 使用 MBean 或 MXBean 來表示一個(gè)資源(下面簡稱 MBean),訪問和管理資源也都是通過 MBean,所以 MBean 往往包含著資源的屬性和操作方法。
JMX 已經(jīng)對 JVM 進(jìn)行了多維度資源檢測,所以可以輕松啟動(dòng) JMX 代理來訪問內(nèi)置的 JVM 資源檢測,從而通過 JMX 技術(shù)遠(yuǎn)程監(jiān)控和管理 JVM。
下面列舉 JMX 對 JVM 的資源檢測類,都可以直接使用。
| 資源接口 | 管理的資源 | Object Name | VM 中的實(shí)例個(gè)數(shù) |
|---|---|---|---|
| ClassLoadingMXBean | 類加載 | java.lang:type= ClassLoading | 1個(gè) |
| CompilationMXBean | 匯編系統(tǒng) | java.lang:type= Compilation | 0 個(gè)或1個(gè) |
| GarbageCollectorMXBean | 垃圾收集 | java.lang:type= GarbageCollector, name=collectorName | 1個(gè)或更多 |
| LoggingMXBean | 日志系統(tǒng) | java.util.logging:type =Logging | 1個(gè) |
| MemoryManagerMXBean | 內(nèi)存池 | java.lang: typeMemoryManager, name=managerName | 1個(gè)或更多 |
| MemoryPoolMXBean | 內(nèi)存 | java.lang: type= MemoryPool, name=poolName | 1個(gè)或更多 |
| MemoryMXBean | 內(nèi)存系統(tǒng) | java.lang:type= Memory | 1個(gè) |
| OperatingSystemMXBean | 操作系統(tǒng) | java.lang:type= OperatingSystem | 1個(gè) |
| RuntimeMXBean | 運(yùn)行時(shí)系統(tǒng) | java.lang:type= Runtime | 1個(gè) |
| ThreadMXBean | 線程系統(tǒng) | java.lang:type= Threading | 1個(gè) |
下面的代碼示例演示了使用 JMX 檢測 JVM 某些信息的代碼示例。
package?com.wdbyte.jmx;
import?java.lang.management.CompilationMXBean;
import?java.lang.management.GarbageCollectorMXBean;
import?java.lang.management.ManagementFactory;
import?java.lang.management.MemoryMXBean;
import?java.lang.management.MemoryManagerMXBean;
import?java.lang.management.MemoryUsage;
import?java.lang.management.OperatingSystemMXBean;
import?java.util.List;
import?java.util.stream.Collectors;
/**
?*?JMX?JVM
?*
?*?@author?https://www.wdbyte.com
?*/
public?class?JavaManagementExtensions?{
????public?static?void?main(String[]?args)?{
????????OperatingSystemMXBean?operatingSystemMXBean?=?ManagementFactory.getOperatingSystemMXBean();
????????String?osName?=?operatingSystemMXBean.getName();
????????String?osVersion?=?operatingSystemMXBean.getVersion();
????????int?processors?=?operatingSystemMXBean.getAvailableProcessors();
????????System.out.println(String.format("操作系統(tǒng):%s,版本:%s,處理器:%d 個(gè)",?osName,?osVersion,?processors));
????????CompilationMXBean?compilationMXBean?=?ManagementFactory.getCompilationMXBean();
????????String?compilationMXBeanName?=?compilationMXBean.getName();
????????System.out.println("編譯系統(tǒng):"?+?compilationMXBeanName);
????????MemoryMXBean?memoryMXBean?=?ManagementFactory.getMemoryMXBean();
????????MemoryUsage?heapMemoryUsage?=?memoryMXBean.getHeapMemoryUsage();
????????long?max?=?heapMemoryUsage.getMax();
????????long?used?=?heapMemoryUsage.getUsed();
????????System.out.println(String.format("使用內(nèi)存:%dMB/%dMB",?used?/?1024?/?1024,?max?/?1024?/?1024));
????????List?gcMXBeans?=?ManagementFactory.getGarbageCollectorMXBeans();
????????String?gcNames?=?gcMXBeans.stream()
????????????.map(MemoryManagerMXBean::getName)
????????????.collect(Collectors.joining(","));
????????System.out.println("垃圾收集器:"?+?gcNames);
????}
}
運(yùn)行可以得到如下結(jié)果:
操作系統(tǒng):Mac OS X,版本:11.6,處理器:12 個(gè)
編譯系統(tǒng):HotSpot 64-Bit Tiered Compilers
使用內(nèi)存:3MB/4096MB
垃圾收集器:G1 Young Generation,G1 Old Generation
3.2. 資源代理 MBean Server
資源代理 MBean Server 是 MBean 資源的代理,通過 MBean Server 可以讓 MBean 資源用于遠(yuǎn)程管理, MBean 資源和 MBean Server 往往都是在同一個(gè) JVM 中,但這不是必須的。
想要 MBean Server 可以管理 MBean 資源,首先要把資源注冊到 MBean Server,任何符合 JMX 的 MBean 資源都可以進(jìn)行注冊,最后 MBean Server 會(huì)暴露一個(gè)遠(yuǎn)程通信接口對外提供服務(wù)。
3.3. JMX 遠(yuǎn)程管理
可以通過網(wǎng)絡(luò)協(xié)議訪問 JMX API,如 HTTP 協(xié)議、SNMP(網(wǎng)絡(luò)管理協(xié)議)協(xié)議、RMI 遠(yuǎn)程調(diào)用協(xié)議等,JMX 技術(shù)默認(rèn)實(shí)現(xiàn)了 RMI 遠(yuǎn)程調(diào)用協(xié)議。
受益于資源管理 MBean 的充分解耦,可以輕松的把資源管理功能擴(kuò)展到其他協(xié)議,如通過 HTTP 在網(wǎng)頁端進(jìn)行管理。
4. JMX 的具體使用
在資源管理 MBean 部分已經(jīng)演示了使用 JMX 獲取 JVM 運(yùn)行信息,那么如果想要自定義一個(gè)資源 MBean 呢?
下面通過一個(gè)例子,模擬一個(gè)內(nèi)存資源 MBean,最后對它進(jìn)行遠(yuǎn)程管理。
4.1. 編寫資源管理 MBean
MBean 的編寫必須遵守 JMX 的設(shè)計(jì)規(guī)范,MBean 很像一個(gè)特殊的 Java Bean,它需要一個(gè)接口和一個(gè)實(shí)現(xiàn)類。MBean 資源接口總是以 MBean 或者 MXBean 結(jié)尾,實(shí)現(xiàn)類則要以接口去掉 MBean 或 MXBean 之后的名字來命名。
編寫一個(gè)內(nèi)存資源管理 MBean 接口,定義如下:
package?com.wdbyte.jmx;
/**
?*?@author?https://www.wdbyte.com
?*/
public?interface?MyMemoryMBean?{
????long?getTotal();
????void?setTotal(long?total);
????long?getUsed();
????void?setUsed(long?used);
????String?doMemoryInfo();
}
然后實(shí)現(xiàn)這個(gè)接口:
package?com.wdbyte.jmx;
/**
?*?@author?https://www.wdbyte.com
?*/
public?class?MyMemory?implements?MyMemoryMBean?{
????private?long?total;
????private?long?used;
????@Override
????public?long?getTotal()?{
????????return?total;
????}
????@Override
????public?void?setTotal(long?total)?{
????????this.total?=?total;
????}
????@Override
????public?long?getUsed()?{
????????return?used;
????}
????@Override
????public?void?setUsed(long?used)?{
????????this.used?=?used;
????}
????@Override
????public?String?doMemoryInfo()?{
????????return?String.format("使用內(nèi)存:?%dMB/%dMB",?used,?total);
????}
}
這個(gè)例子在
MyMemory.java中只有兩個(gè)long?基本類型屬性,所以接口是以 MBean 結(jié)尾。如果資源實(shí)現(xiàn)類中的屬性是自定義實(shí)體類的引用,那么接口就需要以 MXBean 結(jié)尾。
這樣就完成了線程數(shù)量資源 MBean 的創(chuàng)建,其中 total 和 used ?是資源屬性,doMemoryInfo 是資源操作方法。
4.2. 注冊資源到 MBean Server
通過上面的 JMX 架構(gòu)圖,我們知道 MBean 資源需要注冊到 MBean Server 進(jìn)行代理才可以暴露給外部進(jìn)行調(diào)用,所以我們想要通過遠(yuǎn)程管理我們自定義的 MyMemory 資源,需要先進(jìn)行資源代理。
package?com.wdbyte.jmx;
import?java.lang.management.ManagementFactory;
import?javax.management.InstanceAlreadyExistsException;
import?javax.management.MBeanRegistrationException;
import?javax.management.MBeanServer;
import?javax.management.MalformedObjectNameException;
import?javax.management.NotCompliantMBeanException;
import?javax.management.ObjectName;
/**
?*?@author?https://www.wdbyte.com
?*/
public?class?MyMemoryManagement?{
????public?static?void?main(String[]?args)?throws?MalformedObjectNameException,?NotCompliantMBeanException,
????????InstanceAlreadyExistsException,?MBeanRegistrationException,?InterruptedException?{
????????//?獲取?MBean?Server
????????MBeanServer?platformMBeanServer?=?ManagementFactory.getPlatformMBeanServer();
????????MyMemory?myMemory?=?new?MyMemory();
????????myMemory.setTotal(100L);
????????myMemory.setUsed(20L);
????????//?注冊
????????ObjectName?objectName?=?new?ObjectName("com.wdbyte.jmx:type=myMemory");
????????platformMBeanServer.registerMBean(myMemory,?objectName);
????????while?(true)?{
????????????//?防止進(jìn)行退出
????????????Thread.sleep(3000);
????????????System.out.println(myMemory.doMemoryInfo());
????????}
????}
}
啟動(dòng)后可以看到控制臺(tái)每隔三秒打印我們自定義的內(nèi)存信息。
使用內(nèi)存:?20MB/100MB
使用內(nèi)存:?20MB/100MB
不加任何 JVM 參數(shù)啟動(dòng) Java 程序,JMX 只能在當(dāng)前機(jī)器訪問,如果想要通過網(wǎng)絡(luò)在真正的遠(yuǎn)程訪問,那么在啟動(dòng)時(shí)需要指定當(dāng)前機(jī)器 ip 和開放的端口。
$?java?-Dcom.sun.management.jmxremote=true?\??#?開啟遠(yuǎn)程訪問
-Dcom.sun.management.jmxremote.port=8398?\??#?自定義?JMX?端口
-Dcom.sun.management.jmxremote.ssl=false?\??#?是否使用?SSL?協(xié)議,生產(chǎn)環(huán)境一定要開啟
-Dcom.sun.management.jmxremote.authenticate=false?\?#?是否需要認(rèn)證,生產(chǎn)環(huán)境一定要開啟
-Djava.rmi.server.hostname=150.158.2.56?YourClass.java?#?當(dāng)前機(jī)器?ip
4.3. 遠(yuǎn)程管理 jconsole
jconsole 是 Java 自帶的基于 JMX 技術(shù)的監(jiān)控管理工具,如果已經(jīng)配置了 JDK 環(huán)境變量,可以直接控制臺(tái)通過 jconsole 命令啟動(dòng)。

啟動(dòng) jconsole 后會(huì)列出當(dāng)前機(jī)器上的 Java 進(jìn)行,這里選擇自己要監(jiān)控的 Java 進(jìn)程進(jìn)行監(jiān)控,連接后會(huì)提示不安全的協(xié)議,是因?yàn)?Java 程序默認(rèn)啟動(dòng)是不會(huì)配置 HTTPS 協(xié)議的原因。
連接后可以看到多維度的 JVM 監(jiān)控信息,這些信息都是通過讀取 JVM 資源 MBean 信息得到的。

在下面這個(gè)頁面列舉了線程信息,注意最下面的線程信息,可以看到 RMI TCP 線程,這里也證明了 JMX 默認(rèn)通過 RMI 協(xié)議進(jìn)行遠(yuǎn)程管理。

在 MBean 頁面可以瀏覽所有可管理的 MBean 信息,也可以看到我們自定義的 com.wdbyte.jmx 中的內(nèi)存信息,甚至可以直接修改其中的 used 變量。

修改后控制臺(tái)日志立即發(fā)生變化,可以看到已經(jīng)修改成功。
使用內(nèi)存:?20MB/100MB
使用內(nèi)存:?20MB/100MB
使用內(nèi)存:?20MB/100MB
使用內(nèi)存:?30MB/100MB
在操作中可以調(diào)用 doMemoryInfo 方法,調(diào)用后可以看到返回值中使用內(nèi)存已經(jīng)由啟動(dòng)時(shí)的 20MB 更新為 30MB。

一如既往,當(dāng)前文章中的代碼示例都存放在 github.com/niumoo/JavaNotes.
當(dāng)前系列:
Hello world : )?這篇文章就到這里了,我是阿朗,點(diǎn)贊和在看,動(dòng)力無限,求關(guān)注。
