You are viewing a read-only archive of the Blogs.Harvard network. Learn more.
Skip to content

Getting Java JMX to work through firewalls properly

So I read some articles ( [1], [2], [3]) on a really nice piece of technology called Java Management Extensions (JMX) that allows you to:

  • Watch memory usage of a Java JVM in real time
  • Allows this to be done remotely as well as locally
  • Offers a very nice GUI called JConsole
  • Allows you run the garabage collector on the Java VM (I think) in order to try to reclaim unused memory

After reading this I was convinced it was the greatest thing since sliced bread and started enabling it. My first shot at following the instructions above was a failure. I didn’t connect to anything. After some network tracing and head scritching I googled around and found people also had similar issues ( [4], [5], [6], [7], [8] ). The problem revolves around the fact that JMX relies on some older Java technology called RMI.

RMI by default uses a model that is very similar to Sun’s RPC mechanism which means it uses dynamic ports to be able to communicate between a client and a server. Dynamic ports is a big problem when you have a firewall configured on your host. So all the instructions in [1], [2], and [3] are completely useless if you have a firewalled configuration. The port setting that you are giving in those instructions is merely for a serviced called the RMI registry whose main job is to listen for connections then tell the client to connect to some dynamically assigned port on the server.

However it IS possible to write some Java glue code to statically assign a listening port for the RMI server. This is documented in [5], [6], and [7] however I found the documentation a little bit lacking if you don’t really know web programming in Java really well. For example where do you put the code snippets? Why are they using a static method if it’s going into a servlet? (As far as I know you should put it in init() and NOT make it static if you want to follow the Servlet API) Where do you drop this into Tomcat? Do I need to configure one of those aggravating XML config files? So, here is my (sorta) step-by-step process for enabling JMX on Tomcat with a firewall for those of us who aren’t Java experts but can read a little bit on Java Servlet Programming to figure out how to make a simple webapp:

  1. Decide on 2 ports to open up on your firewall.
    I chose 9999 and 3000. 9999 is my RMI Registry listener and 3000 is where the RMI server is
  2. Create a servlet that you will copy as a webapp into your Tomcat directory.
    For my webapp, I call it JMXPortServer
  3. You can use servlet code like the following (Stolen MOSTLY from the links
  4. I created a file called JMXPortServer.java for the object JMXPortServer.
    This file will go into $WEBAPP_HOME/WEB-INF/classes/ when it’s compiled. Here is what went into my JMXPortServer (It’s basically stolen from the links above with some changes to follow the Java Servlet API.
     
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.lang.management.ManagementFactory;
    import java.rmi.registry.LocateRegistry;
    import java.util.HashMap;
    
    import javax.management.MBeanServer;
    import javax.management.remote.JMXConnectorServer;
    import javax.management.remote.JMXConnectorServerFactory;
    import javax.management.remote.JMXServiceURL;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class JMXPortServer extends HttpServlet {
            private static final long serialVersionUID = 1L;
            static JMXConnectorServer cs;
            static String jmxHost;
    
            public void init() throws ServletException {
                    try {
                            // Start rmi registry
                            System.out.println("Create the RMI registry on port 9999");
                            LocateRegistry.createRegistry(9999);
    
                            //      Instantiate the MBean server
                            System.out.println("Get the platform's MBean server");
                            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                            //  Environment map
                            System.out.println("Initialize the environment map");
                            HashMap env = new HashMap();
    
                            //      Create an RMI connector server
                            System.out.println("Create an RMI connector server");
                            JMXServiceURL url = new JMXServiceURL(
                            "service:jmx:rmi://localhost:3000/jndi/rmi://localhost:9999/server");
                            JMXConnectorServer cs =
                                    JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
    
                            // Start the RMI connector server
                            System.out.println("Start the RMI connector server");
                            cs.start();
                    } catch (Exception ex) {
                            System.err.println(ex);
                    }
    
            }
    }
    
    
  5. Create a web.xml that will go in $WEBAPP_HOME/WEB-INF/web.xml.
    Click Here to download a suitable web.xml file ( Viewing XML in HTML is a pain)
  6. In your web.xml file you need to be sure you have an XML tag with load-on-startup.
    This will ensure that this servlet is loaded when Tomcat starts up rather than when Tomcat feels it is appropriate. (I got bit by this one)
  7. Now you should be able to make a warfile from the pieces shown above and drop this into Tomcat.
  8. I think you need to also set -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false in your JAVA_OPTS (JVM Options) (For Tomcat look in $CATALINA_HOME/bin/setenv.sh for a good place to set this)
  9. This warfile you can now just drop into Tomcat and it will always start up the JMX Console so you can monitor things
  10. To connect with JConsole you’ll need to enter the following URL service:jmx:rmi://myserver:3000/jndi/rmi://myserver:9999/server.
    Make sure to substitute the name or IP address of your server with myserver

Caveats

  • I’ve done nothing about security here. From here on you’re on your own
  • The code is REALLY ugly (I could fix a lot) but I know it works
  • I could be doing something wrong but since I’m not a Java ninja… oh well.. someone can email me and point it out

References

AMIS: Modify Spring Beans and log4j levels via JMX
O’Reilly: Monitoring Local & Remote Apps using JMX
JBoss: Using Jconsole
Penrose: Can’t connect to a remote penrose server
Java Forums: Port redirection problems and RMI
Tomcat Bug 39055: Firewall access for JMX
JConsole through a firewall
JMX-RMI-SSL

Be Sociable, Share!