java jmx jconsole jvisualvm

jconsole java



¿Java 6 abre un puerto predeterminado para conexiones remotas JMX? (7)

La documentación parece indicar que el agente JMX utiliza un puerto efímero local, a menos que especifique la siguiente propiedad:

com.sun.management.jmxremote.port=portNum

Se evitan los puertos predeterminados porque podría tener muchas aplicaciones Java en un sistema, y ​​si hubiera un puerto predeterminado, ¡solo se podría administrar una aplicación! La propiedad de configuración anterior se proporciona para el propósito expreso de la administración remota .

Si debe insistir en usar un puerto efímero, entonces debe poder acceder a la URL del agente JMX desde la JVM, a través de la siguiente propiedad del sistema (aunque es probable que sea una dirección local):

com.sun.management.jmxremote.localConnectorAddress

Nota : supongo que siempre se puede abrir un socket en una dirección remotamente disponible y solicitudes de proxy en el socket local, pero usar la opción disponible parece mucho más atractivo.

Mi pregunta específica tiene que ver con JMX como se usa en JDK 1.6: si estoy ejecutando un proceso de Java usando JRE 1.6 con

com.sun.management.jmxremote

en la línea de comandos, ¿elige Java un puerto predeterminado para conexiones JMX remotas?

Antecedentes: actualmente estoy tratando de desarrollar un procedimiento para dar a un cliente que le permita conectarse a uno de nuestros procesos a través de JMX desde una máquina remota. El objetivo es facilitar su depuración remota de una situación que ocurre en una consola de visualización en tiempo real. Debido a su acuerdo de nivel de servicio, están fuertemente motivados para capturar tantos datos como sea posible y, si la situación parece demasiado complicada de arreglar rápidamente, reiniciar la consola de visualización y permitir que se vuelva a conectar al servidor.

Soy consciente de que podría ejecutar jconsole en procesos JDK 1.6 y jvisualvm en procesos post-JDK 1.6.7 dado el acceso físico a la consola. Sin embargo, debido a los requisitos operativos y los problemas de las personas involucradas, estamos muy motivados para captar los datos que necesitamos de manera remota y ponerlos en funcionamiento nuevamente.

EDITAR: soy consciente de la propiedad del puerto de la línea de comando

com.sun.management.jmxremote.port=portNum

La pregunta que estoy tratando de responder es, si no establece esa propiedad en la línea de comando, ¿elige Java otro puerto para la supervisión remota? Si es así, ¿cómo podrías determinar qué podría ser?


Entonces, la respuesta corta a mi pregunta es "no".

Sin embargo, es interesante examinar por qué. Mire la salida netstat de una conexión local válida. Aquí están los puertos que veo abiertos como resultado de una jconsole hace una conexión local consigo misma. Como puede ver, el puerto 1650 es el puerto local que se utiliza para la información 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

Sin embargo, no es suficiente intentar conectar jconsole a localhost:1650 . Lamentablemente, todo lo que lo redireccionará será un mensaje de "Error de conexión: no hay tal objeto en la tabla".

Por lo tanto, la conclusión de mi historia original es que, si vamos a facilitar el monitoreo remoto utilizando JMX para nuestros clientes, realmente necesitamos identificar puertos de acceso remoto individuales para la variedad de procesos de Java que se inician en nuestro sistema. Afortunadamente, todo esto requiere es el uso juicioso del argumento VM:

com.sun.management.jmxremote.port=portNum

donde seguramente tendremos un rango secuencial preespecificado de portNum para que el cliente pueda seleccionar la aplicación remota correcta utilizando el número de puerto.


La documentación sugiere que el agente JMX utiliza un puerto local , algo inalcanzable desde fuera de la máquina, a menos que especifique la siguiente propiedad:

com.sun.management.jmxremote.port=portNum

Esto es por razones de seguridad, así como por la razón dada por el Sr. Potato Head. Por lo tanto, parece que Java 6 no abre un puerto predeterminado de acceso remoto para JMX.

EDITAR: se agregó después de que OP agregara una respuesta con más información.

Otra opción que tiene es crear de algún modo un proxy local que escuche todas las conexiones JMX locales y exporte esta información. De esta forma, no necesita tener una configuración tan mágica de cada instancia de JVM en el servidor. En cambio, el proxy local se puede conectar a todas las JVM a través de JMX y luego de alguna manera exponer esta información de forma remota. No estoy seguro de cómo implementarlo, pero algo como esto puede ser menos trabajo de lo que tendrías que hacer para exponer todas tus JVM de forma remota a través de JMX.


En realidad, hay una propiedad no documentada que puede usar para forzar a JMX a crear conectores accesibles remotamente en números de puerto aleatorios.

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

Las dos últimas propiedades son de la mayor importancia.


He estado trabajando recientemente para descubrir cómo habilitar la administración remota de JMX desde el código de Java, sin requerir que la JVM se haya iniciado con un conjunto de propiedades especiales. La solución que establecí fue iniciar mi propio registro de RMI privado, lo suficientemente fácil, y exponer el servicio de JMX en ese registro. Creo mi propio MBeanServer, luego creo un nuevo JMXConnectorServer. El JMXConnectorServer se crea a través de una llamada como

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

Donde servidor es MBeanServer, y url es una instancia de JMXServiceURL.

La url tiene la forma "servicio: jmx: rmi: /// jndi / rmi: // localhost: / jmxrmi" donde puerto es el número de puerto del registro privado (local). "jmxrmi" es el nombre de servicio estándar para el servicio JMX.

Después de configurar esto y de iniciar el conector, descubro que puedo conectarme a él desde jconsole usando hostname: port.

Esto cubre completamente mis necesidades; Me interesará saber si alguien ve un defecto en este enfoque.

Referencia: JMX Tutorial, Cap. 3


HASTA DONDE SE,

Estas son las posibilidades para conectar un proceso de cliente JMX (una aplicación de administración como jconsole, jmxterm, mc4j, jvmstat, jmxmonitor, jps, ...) a un proceso de servidor JMX (el agente ).

El protocolo que conecta el cliente JMX y el servidor JMX se supone que es ''Java RMI'' (también conocido como ''RMI-JRMP''). Esto debería ser el predeterminado. Se pueden configurar otros protocolos , en particular ''RMI-IIOP'' y ''JMXMP''. Los protocolos especiales son posibles: el proyecto MX4J , por ejemplo, proporciona además SOAP / HTTP y varios protocolos de serialización a través de HTTP.

Consulte los documentos de Sun / Oracle para obtener detalles sobre la configuración.

También eche un vistazo al archivo jre/lib/management/management.properties en su distribución JDK.

Entonces, las posibilidades:

Caso 0: la JVM se inicia sin ninguna configuración particular

Antes de Java 6: La JVM no se comporta como un servidor JMX. Cualquier programa que se ejecute dentro de la JVM puede acceder al MBeanServer de la JVM mediante programación y usarlo para intercambiar datos de manera interesante entre subprocesos o para realizar la monitorización de JVM, pero no es posible administrarlo desde fuera del proceso de la JVM.

Desde Java 6: incluso si no está explícitamente configurado, uno puede acceder a la funcionalidad JMX de la JVM localmente (desde la misma máquina) como se describe en "Caso 1".

Caso 1: la JVM se inicia con -Dcom.sun.management.jmxremote

La JVM está configurada para funcionar como un servidor JMX local (la misma máquina solamente).

En este caso (y, en principio, solo para Sun / Oracle JVM), un cliente JMX puede conectarse al servidor JMX a través de archivos mapeados en memoria que se encuentran en /tmp/hsperfdata_[user] . Esto se alude en la documentación de Sun y se denomina "supervisión local" (y también la API de conexión ). No funciona en sistemas de archivos FAT ya que los permisos no se pueden establecer correctamente allí. Ver esta entrada de blog .

Sun recomienda ejecutar jconsole en una máquina separada del servidor JMX, ya que jconsole aparentemente es un cerdo de recursos, por lo que esta "supervisión local" no es necesariamente una buena idea.

Sin embargo, la monitorización local es bastante segura, solo se puede usar localmente y se puede controlar fácilmente a través de permisos del sistema de archivos.

Caso 2: el servidor JMX se inicia con -Dcom.sun.management.jmxremote.port=[rmiregistryport]

La JVM está configurada para funcionar como un servidor JMX que escucha en varios puertos TCP.

El puerto especificado en la línea de comando será asignado por la JVM y un registro de RMI estará disponible allí. El registro anuncia un conector llamado ''jmxrmi''. Apunta a un segundo puerto TCP asignado aleatoriamente (un puerto "efímero") en el que el servidor JMX RMI escucha y a través del cual se realiza el intercambio de datos real.

Local como se describe en ''Caso 1'' está siempre habilitado en ''Caso 2''.

El servidor JMX escucha en todas las interfaces de forma predeterminada, por lo que puede conectarse (y controlarlo) conectándose localmente a 127.0.0.1:[rmiregistryport] y conectarse de forma remota a [cualquier dirección IP externa]: [algún puerto] de forma remota .

Esto implica que debe observar las implicaciones de seguridad . Puede hacer que la JVM escuche en 127.0.0.1:[rmiregistryport] solo configurando -Dcom.sun.management.jmxremote.local.only=true .

Es bastante desafortunado que uno no pueda especificar dónde se asignará el puerto efímero, siempre se elige aleatoriamente al inicio. ¡Esto puede significar que tu firewall debe convertirse en el queso suizo de los condenados! Sin embargo, hay soluciones temporales . En particular, Apache Tomcat configura el efímero puerto del servidor JMX RMI a través de su JMX Remote Lifecycle Listener . El código para realizar esta pequeña magia se puede encontrar en org.apache.catalina.mbeans.JmxRemoteLifecycleListener .

Si usa este método, también puede asegurarse de que:

  1. El cliente JMX debe autenticarse en el servidor JMX
  2. El intercambio TCP entre el cliente y el servidor está encriptado usando SSL

Cómo se hace eso se describe en la documentación de Sun / Oracle

Otros enfoques

Puede hacer permutaciones interesantes para evitar tener que usar el protocolo RMI. En particular, podría agregar un motor de servlet (como Jetty) a su proceso. A continuación, agregue servlets que traduzcan algún intercambio basado en HTTP internamente en accesos directos al MBeanServer la JVM. Entonces estarías en el ''caso 0'' pero aún tienes capacidades de administración, posiblemente a través de una interfaz basada en HTML. La consola JBoss JMX es un ejemplo de esto.

Más fuera del tema, podría usar SNMP directamente (algo que no he intentado) de acuerdo con este documento .

Mostrar y decir hora

Y ahora es hora de que algún código ilustre un intercambio JXM. Nos inspiramos en un tutorial de Sunoracle .

Esto se ejecuta en Unix. Usamos una JVM que está configurada como un servidor JMX usando:

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

Usamos lsof para verificar qué puertos TCP mantiene abiertos:

lsof -p <processid> -n | grep TCP

Uno debería ver algo como esto, el puerto de registro y el puerto efímero:

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

Usamos tcpdump para inspeccionar el intercambio de paquetes entre el cliente JMX y el servidor JMX:

tcpdump -l -XX port 36828 or port 9001

Configuramos un archivo .java.policy en el directorio de inicio para permitir que el cliente se conecte de manera remota:

grant { permission java.net.SocketPermission "<JMX server IP address>:1024-65535", "connect,resolve"; };

Y luego podemos ejecutar esto y ver qué pasa:

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


Si ejecuta su aplicación dentro del servidor de la aplicación Glassfish, simplemente ejecute el siguiente comando asadmin, deberá reiniciar todos los servidores en ejecución para que el cambio surta efecto.

./asadmin enable-secure-admin

Hay configuraciones adicionales del servidor de Glassfish para habilitar aún más la seguridad, ver más en Conexión remota a Glassfish a través de JMX .