我的具体问题与JDK 1.6中使用的JMX有关:如果我使用JRE 1.6运行Java进程
com.sun.management.jmxremote
在命令行中,Java是否为远程JMX连接选择了一个默认端口?
背景故事:我目前正在尝试开发一个程序,让客户能够通过JMX从远程机器连接到我们的一个进程.目标是实现对实时显示控制台上发生的情况的远程调试.由于他们的服务级别协议,他们强烈有动力捕获尽可能多的数据,如果情况看起来太复杂,无法快速修复,重新启动显示控制台并允许它重新连接到服务器端.
我知道我可以在JDK 1.6进程上运行jconsole,在JDK 1.6.7进程上运行jvisualvm,同时可以访问控制台.但是,由于操作要求和涉及的人员问题,我们强烈要求远程获取我们需要的数据并重新启动和运行.
编辑:我知道命令行端口属性
com.sun.management.jmxremote.port=portNum
我想回答的问题是,如果你没有在命令行设置该属性,Java是否会选择另一个端口进行远程监控?如果是这样,你怎么能确定它可能是什么?
据我所知,
以下是将JMX客户端进程(jconsole,jmxterm,mc4j,jvmstat,jmxmonitor,jps等管理应用程序)连接到JMX服务器进程(代理)的可能性.
连接JMX客户端和JMX服务器的协议被假定为"Java RMI"(又名"RMI-JRMP").这应该是默认值.可以配置其他协议,特别是"RMI-IIOP"和"JMXMP".特殊协议是可能的:例如,MX4J项目还通过HTTP提供SOAP/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客户端可以通过中找到的内存映射文件连接到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地址]:远程连接[某些端口] .
这意味着您必须考虑安全隐患.您只能通过设置使JVM侦听127.0.0.1:[rmiregistryport] -Dcom.sun.management.jmxremote.local.only=true
.
非常不幸的是,人们无法指定临时端口的分配位置 - 它总是在启动时随机选择.这可能意味着您的防火墙需要成为该死的瑞士奶酪!但是,有一些解决方法.特别是,Apache Tomcat通过其JMX远程生命周期监听器设置短暂的JMX RMI服务器端口.可以在org.apache.catalina.mbeans.JmxRemoteLifecycleListener中找到执行这个小魔术的代码.
如果您使用此方法,您还可以确保:
JMX客户端必须向JMX服务器进行身份验证
客户端和服务器之间的TCP交换使用SSL加密
Sun/Oracle文档中描述了如何完成此操作
其他方法
您可以进行有趣的排列,以避免使用RMI协议.特别是,您可以向进程添加servlet引擎(如Jetty).然后添加servlet,将内部基于HTTP的交换转换为对JVM的直接访问MBeanServer
.然后,您将处于'case 0'但仍具有管理功能,可能通过基于HTML的界面.在JBoss的JMX控制台是一个这样的例子.
根据本文档,您可以直接使用SNMP(我没有尝试过).
显示和告诉时间
现在是一些代码来说明JXM交换的时候了.我们从Sunoracle教程中获取灵感.
这在Unix上运行.我们使用配置为JMX服务器的JVM,使用:
-Dcom.sun.management.jmxremote.port=9001
我们用它lsof
来检查它保持打开的TCP端口:
lsof -p
人们应该看到这样的东西,注册表端口和短暂的端口:
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 ":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("", 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 // } }
该文件表明,JMX代理使用本地港口-这是从无法访问外机-除非您指定以下属性:
com.sun.management.jmxremote.port=portNum
这是出于安全原因,也是出于Potato Head先生的原因.因此,看起来Java 6没有为JMX 打开默认的远程可访问端口.
编辑:在OP添加了更多信息的答案后添加.
您拥有的另一个选项是以某种方式创建一个本地代理,该代理侦听所有本地JMX连接并导出此信息.这样,您就不需要在服务器上对每个JVM实例进行这种神奇的配置.相反,本地代理可以通过JMX连接到所有JVM,然后以某种方式远程公开这些信息.我并不确切地知道如何实现这一点,但是这样的事情可能比通过JMX远程公开所有JVM所做的工作要少.
实际上,有一个未记录的属性可用于强制JMX在随机端口号上创建远程可访问的连接器.
-Dcom.sun.management.jmxremote.authenticate="false" -Dcom.sun.management.jmxremote="true" -Dcom.sun.management.jmxremote.ssl="false" -Dcom.sun.management.jmxremote.port="0" -Dcom.sun.management.jmxremote.local.only="false"
最后两个属性是最重要的.