AFAIK,
以下是将 JMX 客户端进程(管理应用程序,如 jconsole、jmxterm、mc4j、jvmstat、jmxmonitor、jps 等)连接到JMX 服务器进程(代理)。
假设连接 JMX 客户端和 JMX 服务器的协议是“Java RMI”(又名“RMI-JRMP”)。这应该是默认值。可以配置other protocols,特别是“RMI-IIOP”和“JMXMP”。特殊协议是可能的:例如,MX4J 项目额外提供了 SOAP/HTTP 和基于 HTTP 的各种序列化协议。
有关配置的详细信息,请参阅Sun/Oracle docs。
还可以查看 JDK 发行版中的文件 jre/lib/management/management.properties。
那么,可能性:
案例 0:JVM 在没有任何特定配置的情况下启动
在 Java 6 之前:JVM 不充当 JMX 服务器。任何在 JVM 内部运行的程序都可以通过编程方式访问 JVM 的MBeanServer 并使用它在线程之间进行有趣的数据交换或进行 JVM 监控,但无法从 JVM 进程外部进行管理。
从 Java 6 开始:即使没有明确配置,也可以本地(从同一台机器)访问 JVM 的 JMX 功能,如“案例 1”中所述。
案例一:JVM以-Dcom.sun.management.jmxremote启动
JVM 被配置为本地(仅限同一机器)JMX 服务器。
在这种情况下(原则上仅适用于 Sun/Oracle JVM),JMX 客户端可以通过在/tmp/hsperfdata_[user] 中找到的内存映射文件连接到 JMX 服务器。这在 Sun 文档中有所提及,称为“本地监控”(以及 Attach API)。它不适用于 FAT 文件系统,因为在那里无法正确设置权限。见this blog entry。
Sun 建议在独立于 JMX 服务器的机器上运行 jconsole,因为 jconsole 显然会占用资源,因此这种“本地监控”的事情不一定是个好主意。
然而,本地监控相当安全,只能在本地使用,并且可以通过文件系统权限轻松控制。
案例2:JMX服务器以-Dcom.sun.management.jmxremote.port=[rmiregistryport]启动
JVM 被配置为作为 JMX 服务器侦听多个 TCP 端口。
命令行上指定的端口将由 JVM 分配,并且在那里可以使用 RMI 注册表。注册表公布一个名为“jmxrmi”的连接器。它指向第二个随机分配的 TCP 端口(一个“临时”端口),JMX RMI 服务器在该端口上进行侦听,并通过该端口进行实际的数据交换。
“案例 1”中描述的本地始终在“案例 2”中启用。
JMX 服务器默认侦听所有接口,因此您可以通过本地连接到 127.0.0.1:[rmiregistryport] 以及远程连接到 [任何外部 IP 地址]:[some端口]远程。
这意味着您必须考虑安全隐患。您只能通过设置-Dcom.sun.management.jmxremote.local.only=true 使JVM 监听127.0.0.1:[rmiregistryport]。
很遗憾,无法指定临时端口的分配位置——它总是在启动时随机选择。这很可能意味着您的防火墙需要成为该死的瑞士奶酪!但是,有workarounds。特别是,Apache Tomcat 通过其JMX Remote Lifecycle Listener 设置临时 JMX RMI 服务器端口。执行这个小魔法的代码可以在org.apache.catalina.mbeans.JmxRemoteLifecycleListener找到。
如果你使用这种方法,你不妨确保:
- JMX 客户端必须向 JMX 服务器进行身份验证
- 客户端和服务器之间的 TCP 交换使用 SSL 加密
the Sun/Oracle documentation 中描述了这是如何完成的
其他方法
您可以做一些有趣的排列来避免使用 RMI 协议。特别是,您可以将 servlet 引擎(如 Jetty)添加到您的流程中。然后添加 servlet,在内部将一些基于 HTTP 的交换转换为对 JVM 的MBeanServer 的直接访问。然后,您将处于“案例 0”,但仍具有管理功能,可能通过基于 HTML 的界面。 JBoss JMX Console 就是一个例子。
更多题外话,你可以根据this document直接使用SNMP(我没试过)。
展示时间
现在是时候用一些代码来说明 JXM 交换了。我们的灵感来自a Sunoracle tutorial。
这在 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 {
// We need a Security Manager (not necessarily an RMISecurityManager)
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
//
// Define a registry (this is just about building a local data structure)
//
final int comSunManagementJmxRemotePort = 9001;
Registry registry = LocateRegistry.getRegistry("<JMX server IP address>", comSunManagementJmxRemotePort);
//
// List registry entries. The client connects (using TCP) to the server on the
// 'com.sun.management.jmxremote.port' and queries data to fill the local registry structure.
// Among others, a definition for 'jmxrmi' is obtained.
//
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);
}
//
// 'Looking up' the entry registered under 'jmxrmi' involves opening and tearing down
// a TCP connection to the 'com.sun.management.jmxremote.port', as well as a TCP
// connection to an ephemeral secondary port chosen at server startup.
// The actual object locally obtained is a "javax.management.remote.rmi.RMIServerImpl_Stub"
// indicating where the ephemeral port is.
// "RMIServerImpl_Stub[UnicastRef [liveRef: [endpoint:[$IP:$EPHEMERAL_PORT](remote),objID:[-62fb4c1c:131a8c709f4:-7fff, -3335792051140327600]]]]"
//
System.out.print("Press enter to get the 'jmxrmi' stub");
System.in.read();
RMIServer jmxrmiServer = (RMIServer)registry.lookup("jmxrmi");
System.out.println(jmxrmiServer.toString());
//
// Now get a "RMI Connection" to the remote. This involves setting up and tearing
// down a TCP connection to the ephemeral port.
//
System.out.print("Press enter to get the 'RMIConnection'");
System.in.read();
RMIConnection rcon = jmxrmiServer.newClient(null);
//
// Ask away. This involves setting up and tearing
// down a TCP connection to the ephemeral port.
//
System.out.print("Press enter to get the 'domains'");
System.in.read();
for (String domain : rcon.getDomains(null)) {
System.out.println("Domain: " + domain);
}
//
// Ok, that will do. For serious applications, we better use the higher-level JMX classes
//
}
}