Java 6是否为JMX远程连接打开了默认端口?

我的具体问题与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代理使用本地端口 – 无法从计算机外部访问 – 除非您指定以下属性:

com.sun.management.jmxremote.port=portNum

这是出于安全原因,也是出于Potato Head先生的原因。 因此,看起来Java 6不会为JMX打开默认的远程可访问端口。

编辑:在OP添加了更多信息的答案后添加。

您拥有的另一个选项是以某种方式创建一个本地代理,该代理侦听所有本地JMX连接并导出此信息。 这样,您就不需要在服务器上具有每个JVM实例的这种神奇配置。 相反,本地代理可以通过JMX连接到所有JVM,然后以某种方式远程公开此信息。 我并不确切地知道如何实现这一点,但是这样的事情可能比通过JMX远程公开所有JVM所做的工作要少。

据我所知,

以下是将JMX客户端进程 (jconsole,jmxterm,mc4j,jvmstat,jmxmonitor,jps等管理应用程序 )连接到JMX服务器进程代理 )的可能性。

连接JMX客户端和JMX服务器的协议被假定为“Java RMI”(又名“RMI-JRMP”)。 这应该是默认值。 可以配置其他协议 ,特别是“RMI-IIOP”和“JMXMP”。 特殊协议是可能的:例如, MX4J项目还通过HTTP提供SOAP / HTTP和各种序列化协议。

有关配置的详细信息,请参阅Sun / Oracle文档 。

还可以查看JDK发行版中的文件jre/lib/management/management.properties

所以,可能性:

案例0:JVM在没有任何特定配置的情况下启动

在Java 6之前:JVM不像JMX服务器那样运行。 在JVM中运行的任何程序都可以以编程方式访问JVM的MBeanServer,并使用它在线程之间进行有趣的数据交换或进行JVM监视,但不能从JVM进程外部进行管理。

从Java 6开始:即使没有明确配置,也可以在本地 (从同一台机器)访问JVM的JMXfunction,如“案例1”中所述。

案例1:JVM以-Dcom.sun.management.jmxremote启动

JVM配置为作为本地 (仅限同一台计算机)的JMX服务器。

在这种情况下(原则上仅适用于Sun / Oracle JVM),JMX客户端可以通过/tmp/hsperfdata_[user]内存映射文件连接到JMX服务器。 这在Sun文档中提到并称为“本地监视”(以及Attach API )。 它不适用于FAT文件系统,因为无法在那里正确设置权限。 请参阅此博客条目 。

Sun建议在与JMX服务器分开的机器上运行jconsole ,因为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地址]:远程连接[某些端口] 。

这意味着您必须考虑安全隐患 。 您只能通过设置-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服务器进行身份validation
  2. 客户端和服务器之间的TCP交换使用SSL加密

Sun / Oracle文档中描述了如何完成此操作

其他方法

您可以执行有趣的排列以避免使用RMI协议。 特别是,您可以向进程添加servlet引擎(如Jetty)。 然后添加servlet,将内部基于HTTP的交换转换为直接访问JVM的MBeanServer 。 然后,您将处于’case 0’但仍具有管理function,可能通过基于HTML的界面。 JBoss JMX控制台就是一个例子。

根据本文档 ,您可以直接使用SNMP(我没有尝试过)。

显示和告诉时间

现在是一些代码来说明JXM交换的时候了。 我们从Sunoracle教程中获取灵感。

这在Unix上运行。 我们使用配置为JMX服务器的JVM,使用:

-Dcom.sun.management.jmxremote.port=9001

我们使用lsof来检查它保持打开的TCP端口:

lsof -p -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 ":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在随机端口号上创建可远程访问的连接器。

 -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" 

最后两个属性是最重要的。

文档似乎表明JMX代理使用本地临时端口, 除非您指定以下属性:

 com.sun.management.jmxremote.port=portNum 

避免使用默认端口,因为您可以在一个系统上安装许多 Java应用程序,如果有默认端口,则只能管理一个应用程序! 提供上述配置属性是为了 远程管理的明确目的

如果必须坚持使用临时端口,那么JMX代理的URL应该可以通过以下系统属性从JVM中访问(尽管这可能是本地地址):

 com.sun.management.jmxremote.localConnectorAddress 

注意 :我猜你总是可以在远程可用的地址上打开一个套接字并将代理请求发送到本地套接字,但使用可用选项似乎更有吸引力!

所以,对我的问题的简短回答是“不”。

但是,检查原因很有意思。 查看有效本地连接的netstat输出。 以下是我看到由于jconsole与自身建立本地连接而打开的端口。 如您所见,端口1650是用于JMX信息的本地端口:

 Proto Local Address Foreign Address State TCP Gandalf:1650 Gandalf:1652 ESTABLISHED TCP Gandalf:1650 Gandalf:1653 ESTABLISHED TCP Gandalf:1650 Gandalf:1654 ESTABLISHED TCP Gandalf:1650 Gandalf:1655 ESTABLISHED TCP Gandalf:1650 Gandalf:1656 ESTABLISHED TCP Gandalf:1652 Gandalf:1650 ESTABLISHED TCP Gandalf:1653 Gandalf:1650 ESTABLISHED TCP Gandalf:1654 Gandalf:1650 ESTABLISHED TCP Gandalf:1655 Gandalf:1650 ESTABLISHED TCP Gandalf:1656 Gandalf:1650 ESTABLISHED 

但是,尝试将jconsole连接到localhost:1650不够的。 遗憾的是,所有能够certificate你是“连接失败:表中没有这样的对象”的消息。

因此,我最初的故事的结论是,如果我们要为客户使用JMX进行远程监控,我们确实需要为在我们的系统中启动的各种Java进程识别独特的单独远程访问端口。 幸运的是,所有这些都需要明智地使用VM参数:

 com.sun.management.jmxremote.port=portNum 

我们几乎肯定会有一个连续预先指定的portNum范围,以便客户可以使用端口号选择正确的远程应用程序。

我最近一直在努力弄清楚如何从java代码启用远程JMX管理,而不需要使用特殊属性集启动JVM。 我解决的解决方案是启动我自己的私有RMI注册表 – 很简单 – 并在该注册表上公开JMX服务。 我创建自己的MBeanServer,然后创建一个新的JMXConnectorServer。 JMXConnectorServer是通过类似的调用创建的

 connector = JXMConnectorServerFactory.newJMXConnectorServer(url, null, server); 

其中server是MBeanServer,url是JMXServiceURL的实例。

url的格式为“service:jmx:rmi:/// jndi / rmi:// localhost:/ jmxrmi”,其中port是(本地)私有注册表的端口号。 “jmxrmi”是JMX服务的标准服务名称。

设置完成后,启动连接器后,我发现可以使用hostname:port从jconsole连接到它。

这完全满足了我的需求; 我很想知道是否有人看到这种方法存在缺陷。

参考: JMX教程,章节。 3

如果您碰巧在Glassfish应用服务器中运行应用程序,只需运行以下asadmin命令,您需要重新启动所有正在运行的服务器才能使更改生效。

./asadmin enable-secure-admin

还有额外的Glassfish服务器配置可以进一步提高安全性,详情请参阅通过JMX远程连接到Glassfish 。