1.概念簡介
JMX(Java Management Extensions)是一個為應(yīng)用程序植入管理功能的框架。JMX是一套標(biāo)準(zhǔn)的代理和服務(wù),
實際上酿炸,用戶可以在任何Java應(yīng)用程序中使用這些代理和服務(wù)實現(xiàn)管理糙申。主要用于對JAVA應(yīng)用程序和JVM進(jìn)行監(jiān)控和管理。
JConsole和JVisualVM中能夠監(jiān)控到JAVA應(yīng)用程序和JVM的相關(guān)信息都是通過JMX實現(xiàn)的媒殉〉5校看下網(wǎng)上的一張結(jié)構(gòu)圖
三層結(jié)構(gòu),分別負(fù)責(zé)通信廷蓉,代理全封,管理資源bean马昙。
最上的通信層:Agent如何被遠(yuǎn)端用戶訪問的細(xì)節(jié)。它定義了一系列用來訪問Agent的接口和組件刹悴,包括Adapter和Connector的描述行楞。
中間的代理層:管理相應(yīng)的資源,并且為遠(yuǎn)端用戶提供訪問的接口土匀。Agent層構(gòu)建在Intrumentation層之上子房,并且使用并管理 Instrumentation層內(nèi)部描述的組件。Agent層主要定義了各種服務(wù)以及通信模型就轧。該層的核心是一MBeanServer,所有的MBean都要向它注冊证杭,才能被管理。注冊在MBeanServer上的MBean并不直接和遠(yuǎn)程應(yīng)用程序進(jìn)行通信妒御,他們通過協(xié)議適配器(Adapter)和連接器(Connector)進(jìn)行通信解愤。通常Agent由一個MBeanServer和多個系統(tǒng)服務(wù)組成隶垮。JMX Agent并不關(guān)心它所管理的資源是什么
基礎(chǔ)管理bean:主要包括了一系列的接口定義和描述如何開發(fā)MBean的規(guī)范礼华。通常JMX所管理的資源有一個或多個MBean組成纺荧,因此這個資源可以是任何由Java語言開發(fā)的組件啡彬,或是一個JavaWrapper包裝的其他語言開發(fā)的資源枢纠。
2. 使用舉例
很多開源框架都會用到j(luò)mx監(jiān)控系統(tǒng)狀態(tài)钙姊,比如我們熟悉的tomcat送浊。下面舉個簡單的例子博秫,說明我們實際使用場景边灭。
需求异希,做一個自定義的classloader類,進(jìn)行加載我指定包路徑下的類存筏,并管理加載了這些類:
-
MBean定義
??根據(jù)Mbean的基礎(chǔ)定義
image.png
我們使用最基本的standard MBean定義接口宠互,此時有以下要求:
- 接口和實現(xiàn)類必須在同一包下
2.接口名字為TestLoaderMBean,那么實現(xiàn)類必須叫TestLoader(MBean前字符串)椭坚,一個單詞不許錯予跌,否則會報錯
因此,我們接口定義
package jmx.beans;
public interface TestLoaderMBean {
/**
* 獲取loader名
* @return
*/
String loaderName();
/**
* 獲取loader總共加載的類數(shù)量
* @return
*/
int loaderClassSize();
int getLoaderClassSize();
/**
* 獲取加載了的所有類名稱
* @return
*/
String loaderClassesNames();
/**
* 加載所有類
*/
void loaderClasses();
/**
* 獲取用戶指定加載的包路徑
* @return
*/
String packageName();
/**
* 銷毀此加載器
*/
void destory();
}
接口定義了loader的管理規(guī)范善茎,聲明了對應(yīng)此loader的管理工作券册,然后定義其實現(xiàn)類
package jmx.beans;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TestLoader implements TestLoaderMBean {
private String name;
private int loaderClassSize;
private List<String> loaderClasss;
private String packageName;
public TestLoader(String name, String packageName) {
this.name = name;
this.packageName = packageName;
}
public TestLoader( ) {
}
@Override
public String loaderName() {
return name;
}
@Override
public int getLoaderClassSize() {
return loaderClassSize;
}
@Override
public int loaderClassSize() {
return loaderClassSize;
}
@Override
public String loaderClassesNames() {
StringBuffer sb=new StringBuffer();
if(loaderClasss!=null){
loaderClasss.stream().forEach(s->{
sb.append(s).append(";\n");
});
}
return sb.toString();
}
@Override
public String packageName() {
return packageName;
}
public void setName(String name) {
this.name = name;
}
public void setLoaderClasss(List<String> loaderClasss) {
this.loaderClasss = loaderClasss;
if (loaderClasss != null) {
loaderClassSize = loaderClasss.size();
}
}
@Override
public void loaderClasses() {
System.out.println("加載" + packageName + "所有類");
Random random = new Random(3);
int i = random.nextInt(3);
try {
TimeUnit.SECONDS.sleep(i);
String class1 = "com.class1";
String class2 = "com.class2";
String class3 = "com.class3";
if (loaderClasss == null) {
loaderClasss = new ArrayList<>();
}
loaderClasss.add(class1);
loaderClasss.add(class2);
loaderClasss.add(class3);
loaderClassSize = loaderClasss.size();
System.out.println("size"+loaderClassSize);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void destory() {
loaderClasss = null;
loaderClassSize = 0;
System.out.println("銷毀所有類");
}
}
模擬類中,注意到getLoaderClassSize()和loaderClassSize()方法是重復(fù)的垂涯,實際上這樣是有區(qū)別的烁焙,一會我們會用到。
最后需要注冊一個代理服務(wù)
package jmx;
import jmx.beans.TestLoader;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class LocalAgent {
private MBeanServer mbs;
public LocalAgent() throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName("jmx:type=testLoader");
TestLoader mbean = new TestLoader("test","com.class");
mbs.registerMBean(mbean, mbeanName);
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String args[]) throws Exception {
LocalAgent agent = new LocalAgent();
}
}
這樣我們就是實現(xiàn)了簡單jmx的管理示例耕赘,當(dāng)然MBeanServer是支持rmi骄蝇、html等遠(yuǎn)程監(jiān)控協(xié)議,如rmi:
package jmx;
import jmx.beans.TestLoader;
import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
public class RmiAgent
{
public RmiAgent() {
try {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName("jmx:type=testLoader");
TestLoader mbean = new TestLoader("test","com.class");
mbs.registerMBean(mbean, mbeanName);
int port=9000;
//這個步驟很重要操骡,注冊一個端口九火,綁定url后用于客戶端通過rmi方式連接JMXConnectorServer
LocateRegistry.createRegistry(port);
//URL路徑的結(jié)尾可以隨意指定赚窃,但如果需要用Jconsole來進(jìn)行連接,則必須使用jmxrmi
String urlStr="service:jmx:rmi:///jndi/rmi://localhost:"+port+"/jmxrmi";
JMXServiceURL url = new JMXServiceURL(urlStr);
JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
jcs.start();
System.out.println("rmi start: "+urlStr);
} catch (MalformedObjectNameException e) {
e.printStackTrace();
} catch (InstanceAlreadyExistsException e) {
e.printStackTrace();
} catch (MBeanRegistrationException e) {
e.printStackTrace();
} catch (NotCompliantMBeanException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
RmiAgent rmiAgent=new RmiAgent();
}
}
當(dāng)然岔激,我們實際使用的是tomcat等容器服務(wù)勒极,所以說下遠(yuǎn)程tomcat開啟jmx監(jiān)控服務(wù)。其實虑鼎,很簡單辱匿,只需要,我們在自定義等setnev.sh中定義
#開啟服務(wù)
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote
#指定ip
-Djava.rmi.server.hostname=172.18.162.10
#指定端口
-Dcom.sun.management.jmxremote.port=9999
#指定是否需要密碼炫彩,如果為true匾七,需要指定用戶密碼
-Dcom.sun.management.jmxremote.authenticate=false
#是否需要ssl
-Dcom.sun.management.jmxremote.ssl=false"
此時再啟動tomcat,就是自動啟動tomcat等jmx了江兢。
對于客戶端來說乐尊,我們有很多,常用的如JVisualVM划址、JConsole等。下面說下兩種的使用方法限府。mac環(huán)境下夺颤,裝好jdk后,這些都是已經(jīng)集成好的(windows在java的bin目錄下可以找到相應(yīng)exe程序)胁勺,打開終端輸入JVisualVM就會自動啟動JVisualVM客戶端(對應(yīng)JConsole一樣的操作流程)
JVisualVM功能是JConsole的升級版世澜,包含了很多功能,可以查看內(nèi)存署穗、cpu寥裂、線程、生成堆快照.....等等案疲。在JVisualVM使用JConsole 封恰,安裝JConsole插件即可,如下我安裝了JConsole褐啡、MBean诺舔、Btrace等插件:
插件安裝后,我們可以就可以在JVisualVM中使用JConsole备畦、MBean低飒、Btrace了。
- 看下對遠(yuǎn)程tomcat對監(jiān)控
上面懂盐,我們已經(jīng)在tomcat的bin目錄下的setenv.sh文件中加入了啟動jmx的腳本褥赊,我們已經(jīng)可以通過客戶端鏈接了,鏈接地址看下配置文件:
#指定ip
-Djava.rmi.server.hostname=172.18.162.10
#指定端口
-Dcom.sun.management.jmxremote.port=9999
端口號是9999莉恼,我們用JVisualVM鏈接
這樣我們就可以通過jmx來管理遠(yuǎn)程tomcat服務(wù)了拌喉,看下tomcat自帶的一些jmx使用
類似這種管理類我們看到了很多速那,可以通過jmx添加用戶,查看部署的app狀態(tài)等等管理模塊司光。
-
本地java服務(wù)監(jiān)控
在我們本地調(diào)試java代碼時也可以使用琅坡,并且,可以使用Btrace進(jìn)行代碼注入残家。
如上邊我本地運(yùn)行了一個LocalAgent的java程序
本地監(jiān)控
對本地的java進(jìn)程就可以做相應(yīng)的監(jiān)控操作了榆俺,并且我們安裝了Btrace插件,可以通過Btrace腳本進(jìn)行代碼注入坞淮,如TestLoader類中有個loaderClasses方法茴晋,在不關(guān)服務(wù)的情況下,我想測一下此方法的執(zhí)行時間回窘。我們寫個Btrace腳本
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
/* put your code here */
@OnMethod(
clazz="jmx.beans.TestLoader",
method="loaderClasses"
)
public static void onWebserviceEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn) {
println(Strings.strcat(Strings.strcat(pcn, "."), pmn));
}
@OnMethod(
clazz="jmx.beans.TestLoader",
method="loaderClasses",
location=@Location(Kind.RETURN)
)
public static void onWebserviceReturn(@ProbeClassName String pcn , @ProbeMethodName String pmn, @Duration long d) {
print("leaving web service ");
println(Strings.strcat(Strings.strcat(pcn, "."), pmn));
println(Strings.strcat("Time taken (msec) ", Strings.str(d / 1000)));
println("==========================");
}
}
腳本中通過onWebserviceReturn方法的@Duration參數(shù)打印了方法的執(zhí)行時間诺擅,這樣就在我們無需關(guān)閉服務(wù)器的情況下,添加了代理代碼啡直。但要注意Btrace執(zhí)行后烁涌,java類的class并不會還原,會一直存在酒觅,所以如果是生產(chǎn)環(huán)境要謹(jǐn)慎撮执。特別@OnMethod通配符的寫法,要修改所有匹配到的類舷丹。如果想了解Btrace更多內(nèi)容抒钱,更多腳本,可以參考官方文檔https://github.com/btraceio/btrace颜凯,里邊有大部分場景的樣例腳本:
好了谋币,上邊說了這么多,都是通過jmx延伸出症概,我們對于應(yīng)用監(jiān)控的一些方法蕾额。當(dāng)然還有很多,如對于遠(yuǎn)程debug的jpda穴豫、對于內(nèi)存分析的mat等等凡简,有時間可以一起討論下。jmx給我提供的是一種監(jiān)控思想精肃,如我們要實現(xiàn)自己的連接池秤涩、管理器等服務(wù),可以借鑒相應(yīng)的監(jiān)控管理方法司抱。