【问题标题】:Does Java 6 open a default port for JMX remote connections?Java 6 是否为 JMX 远程连接打开默认端口?
【发布时间】:2015-03-21 06:48:32
【问题描述】:

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

【问题讨论】:

    标签: java jmx jconsole jvisualvm


    【解决方案1】:

    AFAIK,

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

    假设连接 JMX 客户端和 JMX 服务器的协议是“Java RMI”(又名“RMI-JRMP”)。这应该是默认值。可以配置other protocols,特别是“RMI-IIOP”和“JMXMP”。特殊协议是可能的:例如,MX4J 项目额外提供了 SOAP/HTTP 和基于 HTTP 的各种序列化协议。

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

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

    那么,可能性:

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

    在 Java 6 之前:JVM 不充当 JMX 服务器。任何在 JVM 内部运行的程序都可以通过编程方式访问 JVM 的MBeanServer 并使用它在线程之间进行有趣的数据交换或进行 JVM 监控,但无法从 JVM 进程外部进行管理。

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

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

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

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

    Sun 建议在独立于 JMX 服务器的机器上运行 jconsole,因为 jconsole 显然会占用资源,因此这种“本地监控”的事情不一定是个好主意。

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

    案例2:JMX服务器以-Dcom.sun.management.jmxremote.port=[rmiregistryport]启动

    JVM 被配置为作为 JMX 服务器侦听多个 TCP 端口。

    命令行上指定的端口将由 JVM 分配,并且在那里可以使用 RMI 注册表。注册表公布一个名为“jmxrmi”的连接器。它指向第二个随机分配的 TCP 端口(一个“临时”端口),JMX RMI 服务器在该端口上进行侦听,并通过该端口进行实际的数据交换。

    “案例 1”中描述的本地始终在“案例 2”中启用。

    JMX 服务器默认侦听所有接口,因此您可以通过本地连接到 127.0.0.1:[rmiregistryport] 以及远程连接到 [任何外部 IP 地址]:[some端口]远程。

    这意味着您必须考虑安全隐患。您只能通过设置-Dcom.sun.management.jmxremote.local.only=true 使JVM 监听127.0.0.1:[rmiregistryport]。

    很遗憾,无法指定临时端口的分配位置——它总是在启动时随机选择。这很可能意味着您的防火墙需要成为该死的瑞士奶酪!但是,有workarounds。特别是,Apache Tomcat 通过其JMX Remote Lifecycle Listener 设置临时 JMX RMI 服务器端口。执行这个小魔法的代码可以在org.apache.catalina.mbeans.JmxRemoteLifecycleListener找到。

    如果你使用这种方法,你不妨确保:

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

    the Sun/Oracle documentation 中描述了这是如何完成的

    其他方法

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

    更多题外话,你可以根据this document直接使用SNMP(我没试过)。

    展示时间

    现在是时候用一些代码来说明 JXM 交换了。我们的灵感来自a Sunoracle tutorial

    这在 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。
    • Sun 建议在独立于 JMX 服务器的机器上运行 jconsole,因为 jconsole 显然是一种资源消耗... 这很有趣。您能否提供此信息的来源?
    【解决方案2】:

    documentation 表明 JMX 代理使用 本地 端口——机器外部无法访问的端口——除非您指定以下属性:

    com.sun.management.jmxremote.port=portNum

    这是出于安全原因,也是出于 Potato Head 先生给出的原因。因此,Java 6 似乎没有为 JMX 打开默认的远程访问端口。

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        com.sun.management.jmxremote.port=portNum
        

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

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

        com.sun.management.jmxremote.localConnectorAddress
        

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

        【讨论】:

        • 你是说打开了一个匿名端口(实际上是一个随机数)吗?如果是这样,您知道我们可以预期该端口存在的范围吗?
        • 我希望它是一个非特权(例如 > 1024)端口。
        • 它不能是非特权端口,因为如果你能猜到端口号,就可以从外部访问。
        • @Eddie:端口号与外部是否可达无关,仅与网络接口有关。当我使用 [un]privileged 这个词时,我的意思是按照e-articles.info/e/a/title/Privileged-Ports-of-a-UNIX-machine
        • @Mr Potato Head:我明白你所说的无特权是什么意思。我也明白端口号与外部是否可达无关。但是,您是对的,您可以打开一个端口(特权或非特权),使其外部无法访问,只能在本地访问。
        【解决方案5】:

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

        但是,研究原因很有趣。查看来自有效本地连接的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,以便客户可以使用端口号选择正确的远程应用程序。

        【讨论】:

        • 是的,这就是我在回答中所说的,是吗?
        • 这也可能是个好主意,而不是依赖默认值保持不变。
        【解决方案6】:

        我最近一直在研究如何从 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 服务的标准服务名称。

        设置完成并启动连接器后,我发现我可以使用主机名:端口从 jconsole 连接到它。

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

        参考:JMX Tutorial, Chap. 3

        【讨论】:

          【解决方案7】:

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

          ./asadmin enable-secure-admin

          有额外的 Glassfish 服务器配置可进一步提高安全性,请参阅 Connecting remotely to Glassfish through JMX 了解更多信息。

          【讨论】:

          • 您确实需要按照 JVM 选项的步骤进行操作,这些是一般 Java 应用程序的说明。对于与 Glassfish 应用服务器一起运行的 Java 应用程序,只需使用 asadm 命令即可。这为我节省了很多时间!
          • 另外注意,只有使用 JVM 选项的设置,您仍然会看到运行时异常,Glassfish 将无法启动。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-08-30
          • 2014-12-04
          • 2012-11-08
          • 2010-10-24
          • 2018-01-31
          • 2011-11-02
          • 1970-01-01
          相关资源
          最近更新 更多