Java 6 是否为 JMX 远程连接打开缺省端口?

2022-09-01 13:28:34

我的具体问题与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是否会选择另一个端口进行远程监视?如果是这样,您如何确定它可能是什么?


答案 1

阿法伊克,

以下是将 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开始:即使没有显式配置,也可以像“案例1”中描述的那样在本地(从同一台机器)访问JVM的JMX功能。

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

JVM 配置为作为本地(仅限同一机器)JMX 服务器工作。

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

Sun建议在与JMX服务器分开的机器上运行,因为显然是一个资源浪费,所以这种“本地监控”的事情不一定是一个好主意。jconsolejconsole

但是,本地监视相当安全,只能在本地使用,并且可以通过文件系统权限轻松控制。

案例 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上找到。

如果使用此方法,则最好确保:

  1. JMX 客户机必须向 JMX 服务器进行身份验证
  2. 客户端和服务器之间的 TCP 交换使用 SSL 进行加密

如何做到这一点在 Sun/Oracle 文档中进行了描述

其他方法

您可以执行有趣的排列,以避免必须使用 RMI 协议。特别是,您可以在流程中添加一个 servlet 引擎(如 Jetty)。然后添加 servlet,这些服务将一些基于 HTTP 的交换在内部转换为对 JVM 的直接访问。然后,您将处于“情况0”中,但仍然具有管理功能,可能通过基于HTML的界面。JBoss JMX Console 就是一个例子。MBeanServer

更偏离主题,根据本文档,您可以直接使用SNMP(我还没有尝试过)。

显示和告诉时间

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

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

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

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

lsof -p <processid> -n | grep TCP

人们应该看到这样的东西,注册表端口和临时端口:

java    1068 user  127u  IPv6 125614246                 TCP *:36828 (LISTEN)
java    1068 user  130u  IPv6 125614248                 TCP *:9001  (LISTEN)

我们用于检查 JMX 客户端和 JMX 服务器之间的数据包交换:tcpdump

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

答案 2

文档建议 JMX 代理程序使用本地端口(无法从计算机外部访问的端口),除非您指定以下属性:

com.sun.management.jmxremote.port=portNum

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

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

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


推荐