1. 絮絮叨叨
1.1 Java程序包含哪些线程?
-
使用Java进行多线程编程的人,多少可能都知道执行main()方法的是一个名为main的线程
-
通过new Thread新建线程,若不指定线程名,默认线程名为
Thread-xx
。其中xx
是从0开始的编号public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> System.out.println("I'm " + Thread.currentThread().getName())).start();} }
-
为何默认线程名为
Thread-xx
,通过Thread类的对应构造函数就可以知道public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0); }
-
其实,早在很久之前,自己就有过一个疑问:一个Java程序除了我们所知的main线程、自定义线程,是否还包含其他线程?
-
据我目前掌握的知识,通过jstack命令可以实现
-
在学习并发编程时,发现有一个非常好用的
ThreadMXBean
接口,就可以帮助我们了解Java程序究竟包含哪些线程public static void main(String[] args) {ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);for (ThreadInfo threadInfo : threadInfos) {System.out.println("[" + threadInfo.getThreadId() + "]" + " " + threadInfo.getThreadName());} }
-
获取的线程信息如下,线程10和5,根据自己的经验,应该是idea在运行Java程序时自己添加的线程,以实现监听、响应程序中断
1.2 关于ThreadMXBean接口
-
ThreadMXBean是接口,通过ManagementFactory获取到的肯定是ThreadMXBean接口的实现类的一个实例对象
-
这里使用了Java的多态,将接口的实现类的对象赋值给接口引用
-
ThreadImpl
就是ThreadMXBean接口一个实现类public static ThreadMXBean getThreadMXBean() {return ManagementFactoryHelper.getThreadMXBean(); }public static synchronized ThreadMXBean getThreadMXBean() {if (threadMBean == null) {threadMBean = new ThreadImpl(jvm);}return threadMBean; }
其他使用场景
- 通过ThreadMXBean除了可以获取Java程序中的线程信息,还支持很多其他有用的操作
- 获取程序中死锁的线程ID:
findDeadlockedThreads()
、findMonitorDeadlockedThreads()
( Java ThreadMXBean & 死锁检测 ) - 获取线程数:当前活动线程数
getThreadCount()
、当前活动的守护线程数getDaemonThreadCount()
、活动线程数峰值getPeakThreadCount()
- 获取cpu时间:
getThreadCpuTime(long id)
,获取用户态cpu时间:getThreadUserTime(long id)
- 获取程序中死锁的线程ID:
2. JMX概述
-
通过ThreadMXBean获取Java程序中的线程信息时,资料中有这样的描述
下面使用JMX来查看一个普通的Java程序包含哪些线程
-
看完示例代码后,感觉这就是JMX?好像和自己了解的不太一样?
-
目前,很多大数据组件都可以通过JMX获取节点或者集群的运行情况,这些不同维度的数据一般被称作metric
-
以Presto为例
- presto的指标有:集群或节点内存信息,正在运行的查询数、查询失败率,gc次数和cpu时间等
- 基于jmx这个catalog,执行SQL获取对应metric的数据
- 将metric的值上报到kafka,借助Druid + Granfna实现对集群或节点的实时监控
- 一旦发现某些metric值异常,可以进行监控告警
-
因此,自己对jmx的认知就是:一个可以动态获取程序运行状态的工具,对监控程序的运行非常有用
JMX的定义
- JMX是Java Management Extensions 的缩写,是Java的一种管理扩展工具
- 官方定义: JMX是一套标准的代理和服务,用户可以在任何Java应用程序中使用这些代理和服务实现管理
- 可以通过JDK工具JConsole、网页、客户端与JMX服务器进行交互,从而管理或获取程序的状态
- 中间件软件WebLogic的管理页面、Tomcat、Jboss等都是基于JMX开发的
JMX架构图
- JMX的底层:又称基础层,是被管理的对象MBean。
- 主要有三种:标准MBean、动态MBean和MXBean,本文主要基于标准MBean实现JMX
- 代理层:MBeanServer,对MBean进行注册和管理。
- 远程管理层:又称接入层,可以通过http、RMI、SNMP等方式实现对MBeanServer的远程访问
一些说明
- 如果只把JMX看做是动态获取程序运行状态的工具,是比较狭隘的
- 因为,通过JMX不仅可以获取程序运行状态,还可以向程序传递参数从而影响程序的运行
- MBean中,广义的get方法,提供获取程序运行状态的参数;广义的set方法,可以向程序传递参数
3. 实战
- 通过JMX管理Java程序,需要以下三步
- 定义MBean接口(实现代理的基础),实现MBean:
- MBean接口名必须以MBean为后缀,以MBean实现类为前缀
- 例如,一个MBean接口为HelloMBean,则其实现类为Hello
- 向MBeanServer注册MBean
- 以某种方式进行代理访问,从而实现与MBean的交互
- 定义MBean接口(实现代理的基础),实现MBean:
3.1 基于JConsole的JMX示例
-
创建管理学生的MBean接口:
- 两个属性nam和age,需要针对属性添加getter/setter方法,以保证可读/可写
- 其中,name可读可写,而age只读
- 注意: 获取、设置值的方法不能随意定义为getXXX或者setXXX,否则XXX将被解读成属性。实际上,它可能并不是属性字段
public interface StudentMBean {String getName();void setName(String name);int getAge();void printStudentInfo();String studentInfo(); }
-
实现MBean接口
class Student implements StudentMBean {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic int getAge() {return age;}@Overridepublic void printStudentInfo() {System.out.println("Student: " + name + ", age: " + age);}@Overridepublic String studentInfo() {return "Student: " + name + ", age: " + age;} }
-
向MBeanServer注册MBean
public class JConsoleAgent {public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {// 创建MBeanServerMBeanServer server = ManagementFactory.getPlatformMBeanServer();// 创建ObjectName以唯一标识MBean,其中studentMBean为域名,boy为MBean的名称,可以自由定义ObjectName student = new ObjectName("studentMBean:name=boy");// 将student注册到MBeanServer中,注册时实现了ObjectName与MBean的绑定server.registerMBean(new Student("jack", 24), student);// 程序休眠一段时间,方便观察通过JConsole体验JMXTimeUnit.MINUTES.sleep(30);} }
-
在命令行中输入
jconsole
以启动JConsole工具,选择对应的代理创建连接
-
最终,studentBean的信息如下:
-
name属性为可读可写,可以直接修改name(通过刷新按钮,实现值的修改)
-
printStudentInfo()是一个无参、void方法,直接点击方法名即可运行该方法
-
printStudentInfo方法有输出,最终会在JConsoleAgent的运行界面打印输出信息
3.2 基于网页的JMX示例
-
添加HtmlAdaptorServer的maven依赖
<!-- https://mvnrepository.com/artifact/com.sun.jdmk/jmxtools --> <dependency><groupId>com.sun.jdmk</groupId><artifactId>jmxtools</artifactId><version>1.2.1</version> </dependency>
-
保持MBean不变,创建HttpAdapterAgent,通过对JConsoleAgent进行一些改造,就可以通过网页与JMX进行交互
public class HttpAdapterAgent {public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {// 创建MBeanServerMBeanServer server = ManagementFactory.getPlatformMBeanServer();// 创建ObjectName以唯一标识MBean,其中studentMBean为域名// boy为MBean的名称,可以自由定义ObjectName student = new ObjectName("studentMBean:name=boy");// 将student注册到MBeanServer中,注册时实现了ObjectName与MBean的绑定server.registerMBean(new Student("jack", 24), student);// 创建并注册HtmlAdaptorServer,从而可以通过网页管理MBeanHtmlAdaptorServer adaptorServer = new HtmlAdaptorServer();ObjectName adapter = new ObjectName("httpAdapter:name=web");server.registerMBean(adaptorServer, adapter);// 启动HtmlAdaptorServeradaptorServer.start();} }
-
访问本地的8082端口:http://localhost:8082/,将存在如下网页
-
选择进入studentMBean域、name为boy的MBean,信息展示更加清晰】
3.3 通过客户端进行远程访问
定义支持远程访问的MBeanServer
-
修改Agent使其支持远程访问
public class RemoteAgent {public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException, IOException {// 创建MBeanServerMBeanServer server = ManagementFactory.getPlatformMBeanServer();ObjectName student = new ObjectName("studentMBean:name=girl");server.registerMBean(new Student("lucy", 24), student);// 为MBeanServer注册端口号和urlLocateRegistry.createRegistry(8888);// 若需要支持JConsole链接,url的结尾必须为jmxrmiJMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");// 创建支持远程连接的服务器JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);// 启动服务connectorServer.start();} }
-
此时,尚未定义客户端,可以先通过JConsole进行访问
-
studentMBean域、name为girl的MBean信息如下
定义客户端
- 通过MBeanServer的url实现对指定MBean的管理
- 可以直接通过MBeanServerConnection访问MBean中的属性和方法,也可以通过代理实现访问
- 注意: 属性值定义时首字母虽然为小写,但通过JMX解析后实际是首字母大写
public class Client {public static void main(String[] args) throws IOException, MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, InvalidAttributeValueException {// 创建与server的连接JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");JMXConnector jmxConnector = JMXConnectorFactory.connect(url, null);MBeanServerConnection serverConnection = jmxConnector.getMBeanServerConnection();// 定义想要访问的MBean,与MBeanServer中注册的一致ObjectName student = new ObjectName("studentMBean:name=girl");// 获取MBeanServer所有domainString[] domains = serverConnection.getDomains();System.out.println("MBeanServer存在如下域名:");for (String domain: domains) {System.out.println(domain);}System.out.println("=================================");// 直接修改或访问属性值,属性必须大写serverConnection.setAttribute(student, new Attribute("Name", "grace"));System.out.println("Student: " + serverConnection.getAttribute(student, "Name"));// 直接调用MBean中的方法String info = (String) serverConnection.invoke(student, "studentInfo", null, null);System.out.println(info);// 创建代理实现访问StudentMBean proxy = MBeanServerInvocationHandler.newProxyInstance(serverConnection, student, StudentMBean.class, false);System.out.println("age: " + proxy.getAge());} }
- 访问结果如下
3.4 JVM参数 + JCOnsol,实现JMX远程访问
-
2.1
中基于JConsole的JMX示例,只能通过本地进程的方式进行访问 -
如果想要以最小的代价使其支持远程访问,可以在程序运行时添加以下jvm参数(idea的run Configurations)
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8880 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost
-
然后重新启动JConsoleAgent,通过
localhost:8880
便可实现远程访问
-
注意: 任意一个Java程序,都包含默认的MBean,如下图所示
-
通过java命令启动程序的命令如下,注意:请使用类的完全限定名
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8880 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost vivo.internet.study.jmx.JConsoleAgent
-
当然,同样可以通过Client程序实现与JMX的交互:
service:jmx:rmi:///jndi/rmi://localhost:8880/jmxrmi
4. 总结
-
通过工作所接触的大数据组件,自己对JMX的理解非常表面:就是一个可以动态获取程序运行状态的工具,从而实现对Java程序的监控
-
通过这次的学习,发现JMX的强大远超想象:
- 可以通过MBean获取程序的状态
- 可以修改MBean中属性,从而实现动态向程序传参
- 可以调用MBean中的方法,实现某些操作
- 甚至可以在多个MBean之间进行通信,参考博客:jmx学习
-
JMX的架构:基础层、代理层、接入层,不同的接入方式:JConsole、网页、客户端
-
JMX的实现示例
- 实现JMX的三大步骤:MBean的定义与实现、MBean的注册、以某种方式接入JMX
- 基于JConsole(本地访问)、网页、远程访问(客户端和JConsole),三种不同接入方式的具体实现
- 如果通过JVM参数实现并简化JMX的远程访问实现
-
一些注意事项:
- 通过ObjectName实现MBean实例的唯一标识,包含域名和MBean的name
- MBean的属性一般首字母小写,客户端远程访问时需要首字母大写
- 远程访问时,客户端想访问的Mbean必须与MBeamServer中注册的MBean一致
参考文档:
- jmx学习
- 使用jvisualvm通过JMX的方式远程监控JVM的运行情况