1.概念简介
JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,
实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。主要用于对JAVA应用程序和JVM进行监控和管理。
JConsole和JVisualVM中能够监控到JAVA应用程序和JVM的相关信息都是通过JMX实现的。看下网上的一张结构图
三层结构,分别负责通信,代理,管理资源bean。
最上的通信层:Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。
中间的代理层:管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在Intrumentation层之上,并且使用并管理 Instrumentation层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是一MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过协议适配器(Adapter)和连接器(Connector)进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么
基础管理bean:主要包括了一系列的接口定义和描述如何开发MBean的规范。通常JMX所管理的资源有一个或多个MBean组成,因此这个资源可以是任何由Java语言开发的组件,或是一个JavaWrapper包装的其他语言开发的资源。
2. 使用举例
很多开源框架都会用到jmx监控系统状态,比如我们熟悉的tomcat。下面举个简单的例子,说明我们实际使用场景。
需求,做一个自定义的classloader类,进行加载我指定包路径下的类,并管理加载了这些类:
-
MBean定义
根据Mbean的基础定义
我们使用最基本的standard MBean定义接口,此时有以下要求:
- 接口和实现类必须在同一包下
2.接口名字为TestLoaderMBean,那么实现类必须叫TestLoader(MBean前字符串),一个单词不许错,否则会报错
因此,我们接口定义
package jmx.beans;
public interface TestLoaderMBean {
/**
* 获取loader名
* @return
*/
String loaderName();
/**
* 获取loader总共加载的类数量
* @return
*/
int loaderClassSize();
int getLoaderClassSize();
/**
* 获取加载了的所有类名称
* @return
*/
String loaderClassesNames();
/**
* 加载所有类
*/
void loaderClasses();
/**
* 获取用户指定加载的包路径
* @return
*/
String packageName();
/**
* 销毁此加载器
*/
void destory();
}
接口定义了loader的管理规范,声明了对应此loader的管理工作,然后定义其实现类
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()方法是重复的,实际上这样是有区别的,一会我们会用到。
最后需要注册一个代理服务
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();
}
}
这样我们就是实现了简单jmx的管理示例,当然MBeanServer是支持rmi、html等远程监控协议,如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路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用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();
}
}
当然,我们实际使用的是tomcat等容器服务,所以说下远程tomcat开启jmx监控服务。其实,很简单,只需要,我们在自定义等setnev.sh中定义
#开启服务
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环境下,装好jdk后,这些都是已经集成好的(windows在java的bin目录下可以找到相应exe程序),打开终端输入JVisualVM就会自动启动JVisualVM客户端(对应JConsole一样的操作流程)
JVisualVM功能是JConsole的升级版,包含了很多功能,可以查看内存、cpu、线程、生成堆快照.....等等。在JVisualVM使用JConsole ,安装JConsole插件即可,如下我安装了JConsole、MBean、Btrace等插件:
插件安装后,我们可以就可以在JVisualVM中使用JConsole、MBean、Btrace了。
- 看下对远程tomcat对监控
上面,我们已经在tomcat的bin目录下的setenv.sh文件中加入了启动jmx的脚本,我们已经可以通过客户端链接了,链接地址看下配置文件:
#指定ip
-Djava.rmi.server.hostname=172.18.162.10
#指定端口
-Dcom.sun.management.jmxremote.port=9999
端口号是9999,我们用JVisualVM链接
这样我们就可以通过jmx来管理远程tomcat服务了,看下tomcat自带的一些jmx使用
类似这种管理类我们看到了很多,可以通过jmx添加用户,查看部署的app状态等等管理模块。
-
本地java服务监控
在我们本地调试java代码时也可以使用,并且,可以使用Btrace进行代码注入。
如上边我本地运行了一个LocalAgent的java程序
对本地的java进程就可以做相应的监控操作了,并且我们安装了Btrace插件,可以通过Btrace脚本进行代码注入,如TestLoader类中有个loaderClasses方法,在不关服务的情况下,我想测一下此方法的执行时间。我们写个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参数打印了方法的执行时间,这样就在我们无需关闭服务器的情况下,添加了代理代码。但要注意Btrace执行后,java类的class并不会还原,会一直存在,所以如果是生产环境要谨慎。特别@OnMethod通配符的写法,要修改所有匹配到的类。如果想了解Btrace更多内容,更多脚本,可以参考官方文档https://github.com/btraceio/btrace,里边有大部分场景的样例脚本:
好了,上边说了这么多,都是通过jmx延伸出,我们对于应用监控的一些方法。当然还有很多,如对于远程debug的jpda、对于内存分析的mat等等,有时间可以一起讨论下。jmx给我提供的是一种监控思想,如我们要实现自己的连接池、管理器等服务,可以借鉴相应的监控管理方法。