One of the ways to make Java apps easier to manage and monitor is via JMX. As a first step I set up a simple MBean with a single runtime updateable attribute, to control whether or not my app should do a resource intensive scheduled task.

This gives us a "kill switch" for the scheduled job, to stop any hard work happening without needing to edit a properties file and bounce the app.

This post is mainly a neatened up version of my notes from writing my first few MBeans to give our support team the ability to temporarily throttle back some of the more resource intensive activities of various apps. This gave us an additional, more controllable means to help busy servers handle load spikes, rather than our existing "kill it off and start again" procedures.

MBeans can be used for far more complex and clever application management than what I'm doing here. I'd recommend using them (probably via some helpful framework) for any large enterprise Java apps, to give a decent, extensible, scriptable management interface to your running apps.

Summary

  1. Write an MBean interface. Make sure its name ends in "MBean". Follow standard Java Bean naming rules
  2. Implement the interface in your class to be managed
  3. Add a registration method to tell the MBean server about your MBean class
  4. Recompile, redeploy
  5. Open the JMX tool of your choice. Java Mission Control, JConsole, whatever.

The code

/* The interface - this MUST have the suffix "MBean" according to all the docs */
public interface KillswitchMBean {
    public boolean getFlag();
    public void setFlag(boolean flag);
}
class Killswitch implements KillswitchMBean {

    // implements our read/writeable boolean attribute
    private boolean flag = true;
    public boolean getFlag() { return flag; }
    public void setFlag(boolean flag) { this.flag = flag; }

    // standard constructor stuff
    public Killswitch () {
        // do other contructory type stuff
        ...
        // important bit!
        registerMbean();
    }

    // finds an MBean registry and tells it we exist.
    private void registerMbean() {
        List<MBeanServer> mbservers = MBeanServerFactory.findMBeanServer(null);
        if (mbservers.size() == 0 || mbservers.get(0) == null) {
            LOGGER.warn("No MBean server found, not registering as an MBean.");
            return;
        }
        MBeanServer mbserver = mbservers.get(0);
        ObjectName name = null;
        try {
            name = new ObjectName("Application:Name=Killswitch,Type=Killswitch");
            // in case we're being reployed on a running server
            // This avoids getting a InstanceAlreadyExistsException
            if (mbserver.isRegistered(name)) {
                mbserver.unregisterMBean(name);
            }
            mbserver.registerMBean(this, name);
        } catch (Exception e) {
            LOGGER.warn("Failed to register with MBean server.", e);
        }
    }
    ...
}

That registration method is definitely a chunk of the evil boilerplate Java is infamous for forcing on its devs. This code was written in Java7 days - hopefully there's a much neater way to do this these days - I should do some more reading.

Accessing the MBean

Basically, use any JMX client you prefer. If your support team has some huge enterprise management/monitoring system it probably has a JMX interface. For dev use, or simply trying out JMX stuff, the JConsole and Java Mission Control tools come bundled with the JDK, so they'll do to start with. JMC is prettier, so I'll use that:

  1. run <JDK_HOME>\bin\jmc.exe
  2. File -> Connect -> New Connection
  3. fill in the connection details
    • Host should be a proper hostname, not localhost but something like hipc44 or v-ho-dev
    • Port is likely to be your portbase + "69", that's our usual pattern. Otherwise base.jmx.port in catalina.properties will tell you.
    • User will be "admin"
    • Password will be whatever you've got in the jmx password file for the server
    • Set the Connection name to whatever sounds sensible
  4. Pick the new connection from the available list
  5. Pick "MBean Server" - you should get a control tab opening up with some pretty graphs in it
  6. Pick "MBean Browser" from the tabs at the bottom
  7. The Beans you want will be under "Application" - or use the filter box to search for your MBean name

Final Notes

Updates don't survive restarts

When you change attribute values, or trigger operations, the results do not get persisted outside of memory, unless you handle that in your code. So switching the flag via JMC above would only affect the app until it is next restarted - at which time it'll revert to whatever default is used.

Boolean != boolean

This is important when you're updating the value of a flag attribute on an MBean. If you use a Boolean, you have an object - and that can be set to null. So in JConsole, etc it will cycle thru 3 values when you change a Boolean attribute: TRUE, null, FALSE. This may cause you problems if you leave the flag set to null

So it's safer to use a boolean - i.e. the primitive.

Ignore docs about mbean-descriptor.xml

Some of the older Tomcat docs and web comments talk about creating an mbean-descriptor.xml file in each package that contains MBean classes. There isn't any good description on what those files should contain and it turns out different version of Tomcat used or didn't use them in various ways. Plus it's appserver dependent, so a silly way to do this anyway.

Here we're manually registering our MBean, which also isn't great if we have a whole suite (we should use some form or scanning or bulk registration). But for a small set of custom MBeans, the code above works ok.

Previous Post Next Post

© Me. Best viewed with a sense of humour and a beer in hand.