Developing a custom JMX client

Jconsole is not the only way to monitor a running Java application. Lee Chuk-Munn shows how to develop a custom JMX client to monitor your application.
Written by Lee Chuk-Munn, Contributor
In the past few articles, we have seen how to use jconsole to monitor a running Java application. However, jconsole is not very customizable *. Sometimes, we would like a certain action to happen, like e-mailing a person in response to an event. In situations like these, it would be more desirable to develop a custom JMX client to monitor your application.

In this article, we will look at how to develop a JMX client. In the first section of the article, we will look at how to develop our JMX client; and in the second part, we will look at configuring and running the client. We will be using our Notepad example that we have developed from previous articles.

Writing the JMX Client
Here are the steps that our client will need to perform in order to connect to an MBean.

  1. Attach to the Java virtual machine which is running Notepad
  2. Load the JMX agent, if it is not already running
  3. Connect to the JMX agent
  4. Locate and manipulate the MBean

We will now look at each of these steps in greater detail.

1. Attach to Java Virtual Machine
A JMX client requires a JMXServiceURL to connect to an MBean. The URL points to the JMX server and is typically entered by the user or obtained from the JVM that is running the application. We will assume that we are connecting to a local JVM so what we will do is use the Attach APIs to locate the desired JVM. The Attach API is part located in the com.sun.tools.attach package. The following is a code snipped to locate the JVM running Notepad.jar

//List all the currently running JVMs
List<VirtualMachineDescriptor> list = VirtualMachine.list();
VirtualMachine vm = null;

for (VirtualMachineDescriptor vmd: list)
   //Look for the VM that ends with 'Notepad.jar'
   if (vmd.toString().endsWith("Notepad.jar&")) {
      vm = VirtualMachine.attach(vmd);

The code is quite straightforward. We use the VirtualMachine class to list all the JVMs that are currently running. Once we have the JVM list, a typical application would open a dialog box to ask the user to select the JVM. Since we know exactly which JVM we want, we loop through the JVM list to look for our Notepad JVM. The vmd.toString() method prints the following format:

     sun.tools.attach.LinuxAttachProvider@1bd747e: 25485 Notepad.jar
     sun.tools.attach.LinuxAttachProvider@1bd747e: 29779

We use this fact to match the string ending with 'Notepad.jar'.

2. Load the JMX Agent
Next check if the JMX agent for that JVM has been loaded. To do this, check if the com.sun.management.jmxremote.localConnectorAddress property has been set on the target JVM. If it has not been set, then we know that the JMX agent has not been loaded. We can now go ahead to load the agent. The code snippet shows how this is done:

// com.sun.management.jmxremote.localConnectorAddress
Properties props = vm.getAgentProperties();
String connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);

//If property is not present, load the JMX agent
if (connectorAddress == null) {
   //Agent have not been loaded, we load it
   props = vm.getSystemProperties();
   String home = props.getProperty("java.home");
   String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
   // com.sun.management.jmxremote.port=54321
   vm.loadAgent(agent, CONNECTOR_PORT_PROPERTY);
   //Reload the properties and get the CONNECTOR_ADDRESS_PROPERTY
   props = vm.getAgentProperties();
   connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);}

One other point to note is that we need to specify which port the agent should be running on. So when we load the agent using loadAgent() method, we also need to specify the port. In our example above, the agent is loaded on port 54321.

3. Connect to the JMX Agent
Using the connectorAddress, we now create a connection to the running MBean agent. The following code snippet shows how this is done:

   JMXServiceURL url = new JMXServiceURL(connectorAddress);
   JMXConnector conn = JMXConnectorFactory.connect(url);
   MBeanServerConnection server = conn.getMBeanServerConnection();

4. Locate and Manipulate the MBean
Once we have established a connection to the MBean, we can now query and manipulate the MBean. However, before we can do that, there is just one final hurdle; we have to specify which MBean we are interested in. An application can expose more than one MBean. In part 2 of the JMX series, the NotepadManagement MBean was exposed as 'Notepad:name=notepad'. So we create a ObjectName instance like so

   ObjectName notepad = new ObjectName("Notepad:name=notepad");

The notepad object will now be the name reference to our MBean. Here are some operations that you can perform:

  • Set and get attributes

   //Query an attribute
   System.out.println("count = " + server.getAttribute(notepad, "CharacterCount"));

   //Set an attribute
   Attribute attr = new Attribute("Phrase", "hello");
   server.setAttribute(notepad, attr);

  • Invoke operations

   System.out.println(server.invoke(notepad, "toUpperCase", null, null));

  • Receive notifications

To receive JMX notifications, you have to implement the NotificationListener interface. The interface has only one method, handleNotification(). You will also need to register the listener with the addNotificationListener(). The addNotificationListenerObjectName has four parameters: they are the name of the MBean (ObjectName), the listener, a NotificationFilter for filtering unwanted notifications, and an arbitrary object to be associated with this instance of the listener. The last 2 parameters can be set to null. Here is how a typical registration might look like:

   server.addNotificationListener(notepad, new Main(), null, null);

Running the JMX Client
Before you run the JMX client, you have to set up a password file. The template password file is located at <JDK_HOME>/jre/lib/management directory. Go into that directory where you can rename or copy jmxremote.password.template to the jmxremote.password. Make the jmxremote.password file readable only to the user who will be executing Notepad.jar. For example, if user fred is going to run Notepad.jar, then make jmxremote.password readable only to user fred. On Solaris/Linux the permission for that file will look like this

   -r--------  1 fred users  2856 Nov  2 09:27 jmxremote.password

Now run Notepad.jar. Once Notepad is up, type "hello world" into the editor.

Run the client as follows; you can get the source here.

   java -cp $JAVA_HOME/lib/tools.jar:./Notepad.jar:. Main

or on Windows

   java -cp %JAVA_HOME%\lib\tools.jar;.\Notepad.jar;. Main

Why the reference to tools.jar and Notepad.jar in the classpath? This is because tools.jar contains the Attach API classes. Since the Attach APIs are not part of java or javax packages, they are not in the system's classpath. We will also need to include tools.jar into our classpath when you compile the client.

Notepad.jar is required when the MBean sends a MatchNotification object. If you recall, MatchNotification is the notification object generated by NotepadManagement MBean. The class file is part of Notepad.jar. So when the client receives the notification it needs the class file when it deserializes the event.

Beans, Beans, Beans
The JMX technology is very powerful and flexible. We have, however, only looked at one type of MBean known as the Standard MBean. It is the easiest to write; there are limitations like having all class files present. The other three MBeans are Dynamic, Model and Open. See Wikipedia reference for a quick introduction. O'Reilly has an excellent book on JMX. You can find it here.

*Actually this is not strictly true. JConsole has a set of APIs that allows you to create extensions for it. See JConsole API.

Lee Chuk-Munn has been programming in the Java language since 1996, when he first joined Sun Microsystems in Hong Kong. He currently works as a senior developer consultant and technology evangelist for Technology Outreach at Sun in Singapore. Chuk's focus is in Java APIs, Java EE, Java SE, and Java ME. Chuk graduated in 1987 from the Royal Melbourne Institute of Technology in Melbourne, Australia, where his favorite subject was compiler theory.

Editorial standards