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

28

我需要关于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是否会为远程监视选择另一个端口? 如果是这样,您如何确定它可能是什么?

7个回答

89

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 {
        // 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
        //
    }   
}

我还必须添加-Djava.rmi.server.hostname=127.0.0.1,否则默认主机名为127.0.1.1。 - Jan Kronquist
Sun建议在与JMX服务器分离的机器上运行jconsole,因为jconsole显然会占用大量资源... 这非常有趣。您能否提供此信息的来源? - kevinarpe

38

文档建议JMX代理使用一个本地端口——外部机器无法访问——除非您指定以下属性:

com.sun.management.jmxremote.port=portNum

这是出于安全考虑,以及由Mr Potato Head提供的原因。 因此,看起来Java 6不会为JMX打开默认的远程可访问端口。

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

另一种选择是以某种方式创建一个本地代理,监听所有本地JMX连接并导出此信息。 这样,您不需要对服务器上每个JVM实例进行如此神奇的配置。 相反,本地代理可以通过JMX连接到所有JVM,然后以某种方式远程公开此信息。 我不确定您应该如何实现这一点,但是类似于这样的东西可能比您以其他方式远程公开所有JVM的工作要少一些。


6

实际上,有一个未记录的属性可以用来强制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"

最后两个属性最为重要。

3

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

com.sun.management.jmxremote.port=portNum

默认端口应该避免使用,因为您可能会在一个系统上有许多Java应用程序,如果有一个默认端口,只能管理一个应用程序!以上配置属性是为了远程管理而提供的。
如果您一定要使用临时端口,则JMX代理的URL应该可以通过以下系统属性从JVM内部访问(尽管这可能是本地地址):
com.sun.management.jmxremote.localConnectorAddress

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

你是说会打开一个匿名端口(也就是随机数)?如果是这样,你知道我们可以期望端口存在的范围吗? - Bob Cross
我期望它是一个非特权(例如> 1024)端口。 - David Grant
它不能是非特权端口,因为如果你可以猜到端口号,那么它就可以从外部访问。 - Eddie
@Eddie:端口号与其是否可从外部访问无关,这只取决于网络接口。当我使用术语[非]特权时,我的意思是根据http://e-articles.info/e/a/title/Privileged-Ports-of-a-UNIX-machine/。 - David Grant
@马铃薯先生:我理解你所说的非特权。我也明白端口号与其是否对外可达无关。不过,你说得对,无论是特权端口还是非特权端口,都可以以只在本地可见的方式打开一个端口。 - Eddie

2

所以,我的问题的简短答案是“不行”。

然而,有趣的是探究其中原因。看看有效本地连接的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是不够的。遗憾的是,这只会让您得到一个“连接失败:表中没有此对象”的消息。
因此,我的原始故事的结论是,如果我们要为客户提供使用JMX进行远程监视的便利,我们确实需要为启动在我们系统中的各种Java进程识别唯一的个人远程访问端口。幸运的是,这只需要明智地使用VM参数即可。
com.sun.management.jmxremote.port=portNum

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


是的,这就是我在我的答案中所说的,对吗? - Eddie
这也可能是一个好主意,而不是依赖于默认值保持不变。 - Stefan Thyberg

2

最近我一直在研究如何在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章


0
如果您的应用程序运行在Glassfish应用服务器中,只需运行以下asadmin命令即可。请注意,您需要重新启动所有正在运行的服务器才能使更改生效。
./asadmin enable-secure-admin
还有其他Glassfish服务器配置可以进一步加强安全性,请参阅通过JMX远程连接到Glassfish

你确实需要遵循JVM选项的步骤,这些是针对Java应用程序的通用指令。对于在Glassfish应用服务器上运行的Java应用程序,只需使用asadm命令即可。这为我节省了大量时间! - BJYC
另外需要注意的是,仅使用JVM选项进行设置,您仍然会看到运行时异常,并且Glassfish将无法启动。 - BJYC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接