背景
最近接到一个需求,通过java的方式实现JVM进程jstack信息的采集。
考虑到java 调用linux shell 的实现方式不够优雅
1、有外部的shell 命令调用,无法严格限制资源的使用
2、只能采集本机JVM的信息,无法进行远程信息的调用
正好我们的JVM服务启动了JMX的远程连接配置,因此决定通过JMX RMI的协议,远程调用ThreadMXBean的dumpAllThreads 方法,来实现jstack的信息采集。
JMX的了解 可自行百度,博主理解就是一种对JVM进行远程调用、监控的框架。
JMX Server 的使用
JMX Server 的使用有2中方式:
1、 使用JVM自带的功能通过参数配置开启JMX Server
2、java 代码中自行构造Server,并启动
第一种方式
在jvm 的启动参数增加如下配置:
-Dcom.sun.management.jmxremote=true
-Djava.rmi.server.hostname=127.0.0.1
-Dcom.sun.management.jmxremote.port=50013
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
启动时JVM 会自动启动JMXRMI 的server,供客户端进行连接
第二种方式
参考 代码 simplejmx/JmxServer.java at master · j256/simplejmx · GitHub
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.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class JMXServerTest {
private Registry registry;
private JMXConnectorServer jmxConnectorServer;
private int port;
public JMXServerTest(int port) {
this.port = port;
}
public void start() throws IOException {
if (null == registry && null == jmxConnectorServer) {
registry = LocateRegistry.createRegistry(port);
String serverURL = "service:jmx:rmi://localhost:" + port + "/jndi/rmi://localhost:" + port + "/jmxrmi";
JMXServiceURL jmxServiceURL = new JMXServiceURL(serverURL);
jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, ManagementFactory.getPlatformMBeanServer());
jmxConnectorServer.start();
}
}
public void stop() throws IOException {
if (null != registry && null != jmxConnectorServer) {
jmxConnectorServer.stop();
UnicastRemoteObject.unexportObject((Remote) jmxConnectorServer, true);
}
}
public static void main(String[] args) throws IOException, InterruptedException {
JMXServerTest server = new JMXServerTest(4444);
server.start();
Thread.sleep(60000000);
}
}
JMX Client 的使用
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ThreadInfo;
import java.util.HashMap;
import java.util.Map;
public class JMXClientTest {
public static void main(String[] args) throws IOException, MalformedObjectNameException, ReflectionException, InstanceNotFoundException, IntrospectionException, AttributeNotFoundException, MBeanException {
int port = 4444;
String host = "localhost";
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi");
Map<String, Object> env = new HashMap<String, Object>();
JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
MBeanServerConnection beanConn = jmxc.getMBeanServerConnection();
getThreadsStacks(beanConn);
}
public static void getThreadsStacks(MBeanServerConnection beanConn) throws MalformedObjectNameException, ReflectionException, InstanceNotFoundException, MBeanException, IOException {
StringBuilder sb = new StringBuilder();
CompositeData[] datas= (CompositeData[]) beanConn.invoke(
new ObjectName("java.lang:type=Threading"),
"dumpAllThreads",
new Object[]{false, false}, new String[]{boolean.class.getName(), boolean.class.getName()});
for (CompositeData data : datas) {
ThreadInfo info = ThreadInfo.from(data);
sb.append(info);
}
System.out.println(sb);
}
}