Sunday, July 10, 2011

Picking up old and new instruments: Java M(MX)Beans and Agents

Even though JMX and MBeans have been around for over a decade, there are a lot of Java developers that still have never written or used them. They may have used them with JEE application servers, even VisualVM and JConsole, but for the most part they have been relegated to major Java applications.  I submit that they are just as useful when you encounter smaller Java applications that need remote and graceful control.

Remotely and Gracefully
Remote control means accessing MBeans outside of the JVM that they reside in.  That's simple enough.  Graceful control is more esoteric.  What's graceful to one developer may not be so graceful to another or to the end user.  Suffice it to say that to me graceful means being able to control a Java program, including shutting it down and changing its processing, without introducing instability or uncontrolled results and possible data loss.  Is this type of control only important to larger Java applications?

The Example
The scenario:  You have a batch process that is automated via a Java program.  This program could be executing as part of a Windows Scheduled Task or a Unix/Linux CRON job.  The Java program processes file resources on a scheduled basis using multiple threads.  You need to introduce a method to interrupt processing in a deterministic manner so that there is no ambiguity around which files have been processed.  Part of your program already monitors threads as observables.  So, this is the optimum component to signal to the threads that it is time to stop, after a file is processed successfully and before the next file process is started.

There are several ways that this can be accomplished, not the least of which is a signal file that your threaded app reads between each file that is processes.  However, for deterministic control MBeans are a better choice.  And they are very easy to write.  Listing 1 shows how we would implement a simple MBean to control processing.  The ProgramControlMBean interface specifies our shutdown() method, and the ProgramControl implements this interface.  The MBean also has to register with the MBean server to be accessed and used as an MBean.  Listing 1 contains a private register() method to accomplish registration.  If you have one bean to register in your program, I would let the bean register itself.  For multiple MBeans, I might use a different component within the program to register all the beans.

Listing1
public interface ProgramControlMBean {
    public String shutdown();
}



public class ProgramControl implements ProgramControlMBean {
     public String shutdown() {
          //implement shutdown method
     }
     ...
     
     private void register() {
            try {
                 // Register as MBean with MBean Server
                 MBeanServer server = ManagementFactory.getPlatformMBeanServer();

                 ObjectName name = new ObjectName(
                    "PACKAGE_NAME_HERE:type=ProgramControl");

                 server.registerMBean(this, name);
            } catch (Throwable t) {
                 log.error("ProgramControl could not register with MBean server.");
            }

       }
}


With this combination we now have an MBean that will be available in the same JVM that our program runs, hosted by that JVM's JMX server.  If we have done things correctly we are able to access this MBean using the JMX API from within our program.  We are almost done.  We now need to be able to remotely call the MBean to shutdown our program.  This means accessing the JMX server that hosts this MBean from outside the JVM in which it runs.  The folks that created the JMX API also came up with an easy way to access the JMX server.

Listing 2 shows the JVM arguments that are used to start a Java Agent to allow remote access to MBean running in the JVM's JMX server.  Remote programs can access the JMX server via port 3334, authentication and SSL are not required.

Listing 2
-Dcom.sun.management.jmxremote.port=3334
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false


With these JVM arguments, we can easily start a Java Agent when we start the Java program.  Simply put, Java Agents are programs that are usually independent of other programs (including other agents), but loaded in the same JVM.  They have specific goals that are outside of the main program, like data collection or troubleshooting.

Custom Java Agent for Remote JMX/MBean Access
The Java Agent is necessary since the JMX/MBean server is not accessible by resources outside of the local JVM.  With the JVM arguments, it is very easy to configure an agent to allow us to connect to the target JMX server.  We could also write our own Java Agent as seen in Listing 3.

Listing 3
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
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;

public class RemoteJmxAgent {

    private RemoteJmxAgent() {
    }

    public static void premain(String agentArgs) throws IOException {
        System.setProperty("java.rmi.server.randomIDs", "true");

        // Start an RMI registry on port 3334
        final int port = Integer.parseInt(System.getProperty(
                "us.va.state.vwc.print.agent.port", "3334"));
        LocateRegistry.createRegistry(port);

        // Get handle to JMX MBean server
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();

        // Environment map.
        HashMap env = new HashMap();

        //Setup RMI connector server
        final String hostname = InetAddress.getLocalHost().getHostName();
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://" + hostname
                + ":" + port + "/jndi/rmi://" + hostname + ":" + port
                + "/jmxrmi");
        

        System.out.println("JMX URL::" + url);

        JMXConnectorServer connectorServer = JMXConnectorServerFactory
                .newJMXConnectorServer(url, env, beanServer);

        // Start server
        System.out.println("RMI connector server started on port " + port);

        connectorServer.start();
    }
}


The agent in Listing 3 starts an RMI connector server that will allow remote RMI clients (VisualVM, JConsole, etc.) to connect the JMX MBean server that runs in the JVM that the agent attaches to.  What you should immediately notice is the main() method has been replaced with a premain() method.  That's it, at least at the class definition level.  Getting agents to load when the JVM starts is a little more involved.  First there is the JVM arguments seen in Listing 4; agents must be packaged in their own JAR.

Listing 4
-javaagent agent.jar

And then there is the manifest file entry (Listing 5) that identifies the agent class in the JAR:

Listing 5
Premain-Class: RemoteJmxAgent

If one considers the complexity of writing the RemoteJmxAgent agent class and then adding authentication and transport level security, it just makes more sense that we would use the provided JVM agent configuration as seen in Listing 2.

MXBeans
As of Java 1.6, it is recommended that developers write MXBeans instead of MBeans; moreover, MXBeans are only available in 1.6 and beyond.  MXBeans are very similar to MBeans.  Instead of implementing an interface name ${CLASS_NAME}MBean the MXBean interface is ${CLASS_NAME}MXBean.  They are also registered with the MBean server the same way MBeans are.

The main difference between MBeans and MXBeans is that MXBeans, unlike MBeans, use Java Open Types.  The MXBean implementation maps custom objects returned from MXBeans to a CommonDataSupport type.  This means that the clients using the remote MXBean will not have to have concrete implementations of the classes defined as return types from the MXBean.  There are restrictions on the types returned from MXBeans, but this is still easier than having to remotely implement custom classes like we had to in old days of RMI.  Before using MXBeans, I would be familiar with the specification.

Conclusion
MBeans, MXBeans, and Agents are available to Java developers to help them instrument their Java applications.  I have shown a few examples of these powerful technologies; we must learn to walk before we can run.

No comments:

Post a Comment