AFAIK,
以下是连接JMX服务器进程(代理)和JMX客户端进程(管理应用程序,如jconsole、jmxterm、mc4j、jvmstat、jmxmonitor、jps等)的可能方法。
假定连接JMX客户端和JMX服务器的协议为“Java RMI”(又称“RMI-JRMP”)。这应该是默认设置。可以配置其他协议,特别是“RMI-IIOP”和“JMXMP”。还有可能使用特殊协议:例如,MX4J项目还提供了基于SOAP/HTTP和各种序列化协议的HTTP。
有关配置详细信息,请参阅Sun/Oracle文档。
还要查看JDK发行版中的文件jre/lib/management/management.properties
。
因此,可能性包括:
情况0:JVM没有任何特定配置启动
Java 6之前:JVM不像JMX服务器那样运行。在JVM内部运行的任何程序都可以通过编程方式访问JVM的MBeanServer并使用它在线程之间进行有趣的数据交换或进行JVM监视,但无法从JVM进程外部进行管理。
自Java 6以来:即使没有明确配置,也可以像“情况1”中描述的那样本地(来自同一台机器)访问JVM的JMX功能。
情况1:JVM启动时带有-Dcom.sun.management.jmxremote
JVM被配置为作为一个本地(仅限同一台机器)的JMX服务器工作。
在这种情况下(原则上仅适用于Sun/Oracle JVM),JMX客户端可以通过在/tmp/hsperfdata_[user]
中找到的内存映射文件连接到JMX服务器。这在Sun文档中被提及,并称为“本地监视”(也是Attach API)。在FAT文件系统上无法正常工作,因为权限无法正确设置。请参见此博客文章。
孙公司建议在与JMX服务器分开的机器上运行jconsole
,因为jconsole
似乎是一个资源猪,所以这种"本地监控"并不一定是一个好主意。
然而,本地监控相当安全,只能在本地使用,并且可以通过文件系统权限轻松控制。
情况2:JMX服务器使用-Dcom.sun.management.jmxremote.port=[rmiregistryport]
启动
JVM被配置为作为一个监听多个TCP端口的JMX服务器工作。
命令行指定的端口将由JVM分配,并且一个RMI注册表将在那里可用。注册表广告了一个名为“jmxrmi”的连接器。它指向第二个随机分配的TCP端口(一个“临时”端口),通过该端口进行实际数据交换。
如'情况1'中所述,本地始终在'情况2'中启用。
JMX服务器默认侦听所有接口,因此您可以通过在本地连接到127.0.0.1:[rmiregistryport]以及远程连接到[任何外部IP地址]:[某些端口]来连接它(并控制它)。
这意味着您必须考虑安全影响。您可以通过设置-Dcom.sun.management.jmxremote.local.only=true
使JVM仅侦听127.0.0.1:[rmiregistryport]。
很不幸的是,无法指定临时端口的分配位置 - 它总是在启动时随机选择。这很可能意味着您的防火墙需要成为可怜的瑞士奶酪!然而,有一些
解决方法。特别地,Apache Tomcat通过其
JMX远程生命周期监听器设置了临时JMX RMI服务器端口。执行此小魔术的代码可以在
org.apache.catalina.mbeans.JmxRemoteLifecycleListener中找到。
如果使用此方法,最好确保:
1. JMX客户端必须对JMX服务器进行身份验证
2. 客户端和服务器之间的TCP交换使用SSL加密
如何完成这些操作在
Sun/Oracle文档中有描述。
其他方法
你可以进行有趣的排列组合,避免使用RMI协议。特别地,你可以向进程中添加一个Servlet引擎(例如Jetty)。然后添加Servlet将一些基于HTTP的交换内部转换为对JVM的
MBeanServer
的直接访问。这样你就处于'case 0'但仍具备管理能力,可能通过基于HTML的界面。
JBoss JMX Console是其示例。
更离题的是,你可以直接使用SNMP(我没有尝试过),根据
此文档。
展示时间
现在是展示一些代码来说明JXM交换的时候了。我们从
Sunoracle教程获得灵感。
这在Unix上运行。我们使用配置为JMX服务器的JVM:
-Dcom.sun.management.jmxremote.port=9001
我们使用
lsof
来检查它所持有的TCP端口:
lsof -p <processid> -n | grep TCP
应该能够看到类似这样的内容,注册表端口和临时端口:
java 1068 user 127u IPv6 125614246 TCP *:36828 (LISTEN)
java 1068 user 130u IPv6 125614248 TCP *:9001 (LISTEN)
我们使用
tcpdump
检查 JMX 客户端和 JMX 服务器之间的数据包交换:
tcpdump -l -XX port 36828 or port 9001
我们在主目录中设置了一个文件
.java.policy
,以允许客户端实际上远程连接:
grant {
permission java.net.SocketPermission
"<JMX server IP address>:1024-65535", "connect,resolve";
};
然后我们可以运行这个程序看看会发生什么:
package rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.management.remote.rmi.RMIConnection;
import javax.management.remote.rmi.RMIServer;
public class Rmi {
public static void main(String args[]) throws Exception {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
final int comSunManagementJmxRemotePort = 9001;
Registry registry = LocateRegistry.getRegistry("<JMX server IP address>", comSunManagementJmxRemotePort);
System.out.print("Press enter to list registry entries");
System.in.read();
String[] names = registry.list();
for (String name : names) {
System.out.println("In the registry: " + name);
}
System.out.print("Press enter to get the 'jmxrmi' stub");
System.in.read();
RMIServer jmxrmiServer = (RMIServer)registry.lookup("jmxrmi");
System.out.println(jmxrmiServer.toString());
System.out.print("Press enter to get the 'RMIConnection'");
System.in.read();
RMIConnection rcon = jmxrmiServer.newClient(null);
System.out.print("Press enter to get the 'domains'");
System.in.read();
for (String domain : rcon.getDomains(null)) {
System.out.println("Domain: " + domain);
}
}
}
-Djava.rmi.server.hostname=127.0.0.1
,否则默认主机名为127.0.1.1。 - Jan Kronquist