Monitoring and Managing your Java EE application with JMX

I have recently become a big fan of the Java Management eXtensions (JMX) framework for monitoring and managing our server-side applications that are installed on Glassfish 3.  Setting an application up to be monitored via JMX with either VisualVM or JConsole (both available as part of the JDK) is a relatively easy and straightforward process, so much so, that I don’t know why we have not started using JMX earlier.

We have a application that runs within Glassfish and acts as a proxy from our handheld wireless devices in our warehouses to our ActiveMQ messaging server.  We wanted a way to monitor the health of this application in real-time and looked at using JMX for the task.

The process is fairly painless.  Start with creating an interface with “get” methods for the data that should be available through JMX.  The interface name should be appended with “MXBean” if any of the interface’s methods will need to return values other than primitives.  In the example below getClients() returns a Set of MessagingProxyClientMXBean objects. These MXBeans dive further into device specific info and will also show as a tree structure in VisualVM. (More about these beans later).  Below is the interface for exposing data related to our messaging proxy server.

public interface ProxyServerInfoMXBean {

    public int getConnectedClientCount();

    public String getProxyVersion();

    public int getLastMinuteMessageCount();

    public int getLastHourMessageCount();

    public int getLast24HourMessageCount();

    public int getSubscriberConnectionCount();

    public int getPublisherConnectionCount();

    public int getBrokerConnectionCount();

    public Set<MessagingProxyClientMXBean> getClients();

    public String getBuildDate();

}

As you may expect, the implementation of the interface for the proxy’s MXBean is pretty straightforward as well.

public class ProxyServerInfo implements ProxyServerInfoMXBean {

    protected Map<String, IClientContext> clientMap = new HashMap<String, IClientContext>();
    protected Logger logger;
    protected MessageCounter messageCounter;

    public ProxyServerInfo(Map<String, IClientContext> clientMap) {
        this.clientMap = clientMap;
        logger = Logger.getLogger( getClass() );
        messageCounter = MessageCounter.getInstance();
    }

    @Override
    public int getConnectedClientCount() {
        return clientMap.size();
    }

    @Override
    public Set<MessagingProxyClientMXBean> getClients() {
        Set<MessagingProxyClientMXBean> beanSet = new HashSet<MessagingProxyClientMXBean>();
        for (IClientContext context : clientMap.values()) {
            beanSet.add(new MessagingProxyClient(context));
        }

        return beanSet;
    }

    @Override
    public int getLast24HourMessageCount() {
        return messageCounter.getLast24HourCount();
    }

    @Override
    public int getLastHourMessageCount() {
        return messageCounter.getLastHourMessageCount();
    }

    @Override
    public int getLastMinuteMessageCount() {
        return messageCounter.getLastMinuteMessageCount();
    }

    @Override
    public String getProxyVersion() {
        return Server.getInstance().getVersion();
    }

    @Override
    public String getBuildDate() {
        return Server.getInstance().getBuildDate();
    }

    @Override
    public int getBrokerConnectionCount() {
        return Server.getInstance().getBrokerConnectionCount();
    }

    @Override
    public int getPublisherConnectionCount() {
        return Server.getInstance().getPublisherConnectionCount();
    }

    @Override
    public int getSubscriberConnectionCount() {
        return Server.getInstance().getSubscriberConnectionCount();
    }

}

The final piece to get the MXBean to appear in JConsole is to connect to the JMX instance and register the MXBean.  In the code snippet below, and unique nameString is defined which will determine the bean’s location in JConsole’s tree hierarchy.

protected MBeanServer mbs = null;
protected ProxyServerInfo proxyServerMBean;
protected String nameString = "com.lynden.messaging:type=MessagingProxyClients";

protected ObjectName mBeanName;

public void connect() {
   mbs = ManagementFactory.getPlatformMBeanServer();
   proxyServerMBean = new ProxyServerInfo(clientMap); 
  try { 
    mBeanName = new ObjectName(nameString); 
    try { 
      mbs.unregisterMBean(mBeanName); 
    } catch (Exception ex) { 
       //there may not already be a bean there to unregister. 
    }
      mbs.registerMBean(proxyServerMBean, mBeanName); 
    } catch (Exception ex) { 
      throw new IllegalStateException(ex);
   }
 }

Launching VisualVM and connecting to Glassfish’s JMX port, the “com.lynden.messaging” node appears and shows the results of the MessagingProxyClients info that was defined in the class and interface above.

image


Nesting MXBeans

As previously mentioned, MXBeans can be nested, which in this case can be used to display information about the individual clients that are connected to the proxy server.

Just like in the previous example an MXBean interface can be created to represent the various pieces of data about the client that we want available in JMX, along with an implementation of the interface (Not shown).

Registering the MXBean for the client is similar to the above process for registering the MXBean for the server itself, except for the “clientNameString” variable shown below, which shows a type=MessagingProxyClients,name=<some-client-id>.  Once that is in place, any new clients that connect to the proxy will automatically appear in JConsole without needing any sort of refresh.

String clientNameString = "com.lynden.messaging:type=MessagingProxyClients,name=";

MessagingProxyClient mpc = new MessagingProxyClient(client);
ObjectName clientName = new ObjectName(clientNameString + client.getClientId()); 
mbs.registerMBean(mpc, clientName); 
clientObjectList.add(clientNameString + client.getClientId());

When clients disconnect from the proxy, the client’s MXBeans are deregistered from JMX in a similar manner.

String clientNameString="com.lynden.messaging:type=MessagingProxyClients,name=";
mbs.unregisterMBean(new ObjectName(clientNameString + client.getClientId()));
clientObjectList.remove(clientNameString + client.getClientId());

Below is a screenshot from VisualVM of an expanded proxy server node, displaying all the connected client instances.  The right side of the window shows the various properties that are available from the client’s MXBean.

image


JMX Operations

One final piece of JMX functionality that can be useful is the ability to execute methods on the MXBeans (called Operations) in VisualVM/JConsole.  In order to create a method available via JMX it only needs to be in the MXBean interface and implementation, and not conform to the standard JavaBean “getter” format.  In our client’s MXBean, in addition to the getter methods, the bean also contained a removeClient() method which would force the client’s connection to the proxy to close.  Below is a snippet from the client’s MXBean’s implementation.

@Override
public void removeClient() {
     client.setFlaggedForDisposal(true);
}

Below is a screenshot from VisualVM showing that the method can be invoked from the bean’s “Operations” tab by clicking the “removeClient” button.

image

With this information available from our production servers via JMX we now have a very useful tool for debugging issues when they arise in production that will give us greater insight than the application’s log files alone.

twitter: @RobTerp

4 thoughts on “Monitoring and Managing your Java EE application with JMX

Leave a Reply to Mehdi HeidarzadehCancel reply