July 24th, 2012

Whilst upgrading our Tomcat instance to the 7.0.x series the other day I noticed the JMX remote life cycle listener in the Tomcat listener page of the configuration reference. In the past I’ve found monitoring HEAP behaviour invaluable for debugging memory issues and also thought accessing Tomcat’s ever expanding implementation of MBean might prove useful and so thought I’d spend a little time trying to get a jconsole running on my PC to connect to our remote Tomcat instance on Linux.

This proved tricky to work out but it is relatively straight forward once you know how. I chose to connect via a SSH tunnel as this seems to me the easiest way to get access securely.

Server side changes

Additional jar required

Download the JMX remote jar (catalina-jmx-remote.jar) to $CATALINA_HOME/lib from the Tomcat download page (extras section).

Configure the listener in server.xml

The JMX remote lifecycle listener tweaks the behaviour of the RMI server exposing its MBeans so that the rmi server port is fixed.  Usually just the registry port is fixed.   You need to configure Tomcat to use this listener by declaring it in $CATALINA_BASE/conf/server.xml using two available ports on your server, documented here as ${rmi.registry.port} and ${rmi.server.port}.

<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
        rmiRegistryPortPlatform="${rmi.registry.port}"
        rmiServerPortPlatform="${rmi.server.port}"
        useLocalPorts="true" />

The useLocalPorts prevents remote access.  We’re going to use an SSH tunnel so this is a vital security step.

New system properties for Tomcat at startup

As I’m using an SSH tunnel to the server and we have a limited and trusted set of system administrators who have access I can disable authentication.  SSL can also be disabled because we’re using SSH.  Add the following to your init.d script or equivalent start-up script.

     -Dcom.sun.management.jmxremote.authenticate=false \
     -Dcom.sun.management.jmxremote.ssl=false \

Client side changes

Configure putty ssh tunnel

I use putty from my windows PC to connect to our linux servers over ssh.  Putty supports ssh tunnels and we need to create 2 loopbacks, one for each of the rmi ports.  We need to map local ports to the same remote server port.

Assuming you already have a saved session that connects to the server:

  • right click on putty icon in sys tray
  • click new session
  • select existing session and click load
  • navigate to connection / ssh tunnels
  • add first forwarded port
    • source port: ${rmi.registry.port}
    • destination: 127.0.0.1:${rmi.registry.port}
    • click add
  • add second forwarded port
    • source port: ${rmi.server.port}
    • destination: 127.0.0.1:${rmi.server.port}
    • click add
  • save session – you might want to create a new session with a different name but you can also use the existing one
  • click open

Once your putty console is connected to the server putty will be listening on the two local ports and forwarding these to the remote server.  Now all we need to do is configure jconsole to connect.

Connect using jconsole

Download the JMX remote jar to your PC.  You need it to connect.  Open a command prompt and run the following (editing the location of the downloaded jar and inserting your ports

"%JAVA_HOME%\bin\jconsole" -J"-Djava.class.path=%JAVA_HOME%jre\lib\jsse.jar;%JAVA_HOME%\lib\jconsole.jar;C:\downloads\catalina-jmx-remote.jar" service:jmx:rmi://localhost:${rmi.server.port}/jndi/rmi://localhost:${rmi.registry.port}/jmxrmi

Connect using Java VisualVM

Open Java VisualVM

File / add JMX connection / service:jmx:rmi://localhost:${rmi.server.port}/jndi/rmi://localhost:${rmi.registry.port}/jmxrmi

Debugging issues

Check you have your registry port and server port that right way round in your connection string!

Server side

I found setting the system property java.rmi.server.logCalls=true and then monitoring standard error very helpful in debugging what was going on.  There are other Java RMI properties and Sun RMI properties you can set that might also be helpful (client side as well).

Client side

For Jconsole connections if you add the -debug switch and configure logging this can give you a few vital clues as to why the connection is failing.

Create a logging.properties file

handlers = java.util.logging.ConsoleHandler
.level = INFO

java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = \
java.util.logging.SimpleFormatter

// Use FINER or FINEST for javax.management.remote.level - FINEST is
// very verbose...
javax.management.level = FINEST
javax.management.remote.level = FINER

Edit your command to look like:

"%JAVA_HOME%\bin\jconsole" -J"-Djava.class.path=%JAVA_HOME%jre\lib\jsse.jar;%JAVA_HOME%\lib\jconsole.jar;C:\downloads\catalina-jmx-remote.jar" -J-Djava.util.logging.config.file=logging.properties service:jmx:rmi://localhost:${rmi.server.port}/jndi/rmi://localhost:${rmi.registry.port}/jmxrmi

Thank you

Thank you to djktno for his excellent blog post that gave me the last few vital clues for getting this working.