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 是否会选择另一个端口进行远程监控?如果是这样,你怎么能确定它可能是什么?

4

7 回答 7

88

AFAIK,

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

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

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

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

所以,可能性:

案例 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 客户端可以通过/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 地址]:[some port] 来连接到它(并控制它) .

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

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

如果您使用此方法,您不妨确保:

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

Sun/Oracle 文档中描述了这是如何完成的

其他方法

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

更题外话,您可以根据此文档直接使用 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
        //
    }   
}
于 2011-08-08T16:48:51.440 回答
38

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

com.sun.management.jmxremote.port=portNum

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

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

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

于 2009-02-05T15:28:24.383 回答
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"

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

于 2011-11-25T08:22:03.443 回答
3

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

com.sun.management.jmxremote.port=portNum

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

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

com.sun.management.jmxremote.localConnectorAddress

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

于 2009-02-05T15:15:53.363 回答
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

但是,尝试连接jconsolelocalhost:1650. 可悲的是,所有能让你受益的是“连接失败:表中没有这样的对象”消息。

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

com.sun.management.jmxremote.port=portNum

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

于 2009-02-10T01:54:38.463 回答
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 服务的标准服务名称。

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

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

参考:JMX 教程,第一章。3

于 2012-07-23T18:21:05.913 回答
0

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

./asadmin 启用-安全-管理员

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

于 2015-03-20T22:48:32.917 回答