AFAIK,
以下是将JMX 客户端进程(像 jconsole、jmxterm、mc4j、jvmstat、jmxmonitor、jps 等管理应用程序)连接到JMX 服务器进程(代理)的可能性。
假设连接 JMX 客户端和 JMX 服务器的协议是“Java RMI”(又名“RMI-JRMP”)。这应该是默认值。可以配置其他协议,特别是“RMI-IIOP”和“JMXMP”。特殊协议是可能的:例如,MX4J项目另外提供了 SOAP/HTTP 和各种基于 HTTP 的序列化协议。
有关配置的详细信息,请参阅Sun/Oracle 文档。
还可以查看jre/lib/management/management.properties
JDK 发行版中的文件。
所以,可能性:
案例 0:JVM 在没有任何特定配置的情况下启动
在 Java 6 之前:JVM 不充当 JMX 服务器。在 JVM 内部运行的任何程序都可以通过编程方式访问 JVM 的MBeanServer并使用它来进行线程之间有趣的数据交换或进行 JVM 监控,但无法从 JVM 进程外部进行管理。
从 Java 6 开始:即使没有明确配置,也可以在本地(从同一台机器)访问 JVM 的 JMX 功能,如“案例 1”中所述。
案例1:JVM启动-Dcom.sun.management.jmxremote
JVM 配置为作为本地(仅限同一机器)JMX 服务器工作。
在这种情况下(原则上仅适用于 Sun/Oracle JVM),JMX 客户端可以通过/tmp/hsperfdata_[user]
. 这在 Sun 文档中被提及并称为“本地监控”(以及Attach API)。它不适用于 FAT 文件系统,因为在那里无法正确设置权限。请参阅此博客条目。
Sun 建议jconsole
在与 JMX 服务器分开的机器上运行,因为这jconsole
显然是一种资源消耗,因此这种“本地监控”事情不一定是个好主意。
然而,本地监控相当安全,只能在本地使用并且可以通过文件系统权限轻松控制。
案例 2:JMX 服务器启动时使用-Dcom.sun.management.jmxremote.port=[rmiregistryport]
JVM 被配置为在多个 TCP 端口上侦听的 JMX 服务器。
命令行上指定的端口将由 JVM 分配,并且在那里可以使用 RMI 注册表。注册表公布一个名为“jmxrmi”的连接器。它指向第二个随机分配的 TCP 端口(一个“临时”端口),JMX RMI 服务器在该端口上进行侦听,并通过该端口进行实际的数据交换。
“案例 1”中描述的本地始终在“案例 2”中启用。
JMX 服务器默认侦听所有接口,因此您可以通过本地连接到 127.0.0.1:[rmiregistryport] 以及远程连接到 [任何外部 IP 地址]:[some port] 来连接到它(并控制它) .
这意味着您必须考虑安全隐患。您只能通过设置让 JVM 监听 127.0.0.1:[rmiregistryport] -Dcom.sun.management.jmxremote.local.only=true
。
不幸的是,无法指定临时端口的分配位置——它总是在启动时随机选择。这很可能意味着您的防火墙需要成为该死的瑞士奶酪!但是,有一些解决方法。特别是,Apache Tomcat 通过其JMX Remote Lifecycle Listener设置临时 JMX RMI 服务器端口。执行这个小魔法的代码可以在org.apache.catalina.mbeans.JmxRemoteLifecycleListener中找到。
如果您使用此方法,您不妨确保:
- JMX 客户端必须向 JMX 服务器进行身份验证
- 客户端和服务器之间的 TCP 交换使用 SSL 加密
Sun/Oracle 文档中描述了这是如何完成的
其他方法
您可以做一些有趣的排列来避免使用 RMI 协议。特别是,您可以将 servlet 引擎(如 Jetty)添加到您的流程中。然后添加 servlet,在内部将一些基于 HTTP 的交换转换为对 JVM 的直接访问MBeanServer
。然后,您将处于“案例 0”,但仍具有管理功能,可能通过基于 HTML 的界面。JBoss JMX 控制台就是一个例子。
更题外话,您可以根据此文档直接使用 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 {
// 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
//
}
}