MXBeans in Java SE 6: Bundling Values Without Special JMX Client Configurations

   
By Éamonn McManus With Janice J. Heiss, September 2006  
Java Management Extensions (JMX)MBeansJava Platform, Standard Edition 6

Here's how it works.

Note: Any API additions or other enhancements to the Java SE platform specification are subject to review and approval by the JSR 270 Expert Group.

Bundles of Values

When designing an MBean, developers may need to have a bundle of associated values.

Figure 1: Snapshot of Memory Usage

A snapshot will typically have implicit constraints based on the meanings of the values. In this case, the values of init, used, and committed will all be less than the value of max.

Each Value as a Separate Attribute

One way to model this is to make each value an attribute of the MBean:

public interface MemoryMBean {
    public long getInit();
    public long getUsed();
    public long getCommitted();
    public long getMax();
}

public class Memory implements MemoryMBean {
    ...
    public long getUsed() {
         return currentUsed();
    }
    private static native long currentUsed();
    ...
}
 

But this approach could result in an inconsistent set of values. For example, the client code might look like the following sample:

    MBeanServerConnection mbsc = ...
                  
something
...; // Either call getAttribute directly... long max = (Long) mbsc.getAttribute(memoryMBeanName, "Max"); long used = (Long) mbsc.getAttribute(memoryMBeanName, "Used"); // or make a proxy. MemoryMBean memory = (MemoryMBean) MBeanServerInvocationHandler.
newProxyInstance
( mbsc, memoryMBeanName, MemoryMBean.class, false); long max = memory.getMax(); long used = memory.getUsed();
 

This code could result in a value of used that is greater than the value of max, even though that makes no sense. If the developer retrieved max again, the new value would indeed be greater than the used value.

You might try to circumvent this problem by using the plural getAttributes instead of the singular getAttribute:

    MBeanServerConnection mbsc = ...
                  
something
...; AttributeList attrs = mbsc.getAttributes(memoryMBeanName, new String[] {"Max", "Used"}); if (attrs.size() != 2) throw new UnpleasantException("..."); long max = (Long) ((Attribute) attrs.get(0)).getValue(); long used = (Long) ((Attribute) attrs.get(1)).getValue();
 

This code is more complicated, provides no option of making a proxy, and fails to solve the problem. Ultimately, the MBean Server will handle the getAttributes call by calling the MBean's getMax() method and then calling the MBean's getUsed() method. Although the interval between these calls might be smaller, the values might still be inconsistent.

You could solve this problem by making the MBean a Dynamic MBean rather than a Standard MBean and by implementing the DynamicMBean.getAttributes method so that it returns a consistent set of attributes. But that's a lot of work.

Furthermore, the task of breaking a complex set of values out into separate attributes can quickly become unmanageable. Suppose that instead of being plain long values, each value of init, used, committed, and max was itself a bundle of statistical values, such as min, current, max, and average, as in Figure 2.

Figure 2: A Complex Set of Values Broken Into Separate Attributes

This would require you to define 16 attributes, such as UsedMin, UsedCurrent, CommittedMin. Is that something you want to do? Probably not something you want to do.

Defining a Type for the Bundle of Values

An alternative approach is to define a Java class that represents the relevant bundle of values. For example:

public class MemoryUsage implements Serializable {
    private static final long serialVersionUID = 
        -3529132954339066973L;

    public MemoryUsage(
            long init, long used, long committed, long max) {
         this.init = init;
         ...
    }
    public long getInit() {
         return this.init;
    }
    public long getUsed() {...}
    public long getCommitted() {...}
    public long getMax() {...}
}

public interface MemoryMBean {
    public MemoryUsage getMemoryUsage();
}

public class Memory implements MemoryMBean {
    public MemoryUsage getMemoryUsage() {
         return memoryUsageSnapshot();
    }
    private static native MemoryUsage
            memoryUsageSnapshot();
    ...
}
 

The client code might look like this:

    MBeanServerConnection mbsc = ...
                  
something
...; MemoryUsage mu = (MemoryUsage) mbsc.getAttribute(memoryMBeanName, "MemoryUsage"); long max = mu.getMax(); long used = mu.getUsed();
 

This looks much nicer. That the four values are related is explicitly reflected in the MBean's interface, and it is no longer possible to inadvertently get an inconsistent set of values. You can create a proxy rather than calling getAttribute and have more than one attribute that returns the bundle type, for example, getHeapMemoryUsage and getNonHeapMemoryUsage.

But this presents a subtle problem: For the client code to compile and run, you must now supply it with a copy of the compiled MemoryUsage class. Suppose the client is a generic one such as jconsole. If you connect jconsole to our MemoryMBean, by default you'll see what appears in Figure 3.

Figure 3: What MemoryMBean looks like in jconsole

Figure 3 shows the MemoryUsage attribute as unavailable because jconsole doesn't know the class MemoryUsage. When jconsole tries to retrieve the attribute's value, it gets a ClassNotFoundException.

Of course, you can launch jconsole with a classpath that includes the MemoryUsage class, but that is not a good solution. jconsole can connect to any number of remote MBean Servers. Imagine if you had to assess all the classes used by any of those MBean Servers and then put them all in jconsole's classpath. Furthermore, what if some of the MBean Servers used the same class but with different versions? This quickly gets messy.

Code Downloading

One solution that developers sometimes propose to certain classpath problems is remote method invocation (RMI) code downloading. Jini technology uses this heavily, for example. The idea is that if jconsole can't find a class such as MemoryUsage, it can download it from an HTTP server associated with the remote MBean Server -- a nice idea, if you can make it work.

But you will need to arrange for your HTTP server to have the right classes and keep them in sync with the MBean Server, configure any firewalls so that the HTTP connection is allowed, set up security in jconsole so that it does not allow the downloaded classes to do anything they shouldn't, and use only the RMI connector. In a sense, this is the price of the ticket for entering the Jini technology universe. Once you're in, you'll find plenty of great stuff. But if all you want to do is access bundles of values, the price is too high.

A recent paper by Michael Warres offers an excellent analysis of some of the problems you can encounter with code downloading.

 
 
 
MXBeans: Avoiding Problems With Classes

MXBeans extend the MemoryUsage approach that this article just described in a way that is designed to avoid the problem with classes. Éamonn McManus, Mandy Chung, and Sanjay Radia originally designed MXBeans as part of JSR 174, Monitoring and Management Specification for the Java Virtual Machine. MXBeans are used in the java.lang.management package in J2SE 5.0 and later. In particular, the MemoryUsage example in the previous section is inspired by the MemoryUsage class from that package and by the MemoryMXBean interface that uses it.

How will jconsole work if you change the MemoryMBean into a MemoryMXBean. Figure 4 shows you.

Figure 4: What MemoryMXBean looks like in jconsole

It looks a bit cryptic, but all you have to do is double-click on the javax.management.openmbean.CompositeDataSupport value to get the result in Figure 5.

Figure 5: Double-Clicking on javax.management.openmbean.CompositeDataSupport

The bundle of information you are interested in appears, without your having had to configure JConsole in any special way to see it.

How Do I Write an MXBean?

To get the result in Figure 5, you must change the Memory MBean from a Standard MBean to an MXBean. This requires very few modifications:

Link Link
 

As you can see, the only change from the Standard MBean example to the MXBean example is that the MemoryMBean interface has been renamed MemoryMXBean.

Recall that with a Standard MBean, you define an interface called FooMBean and a class called Foo that implements it. This Standard MBean example shows an interface MemoryMBean and a class Memory that implements it.

The MXBean convention requires that the interface be called FooMXBean . The class can be called Foo as here, or in fact anything else, for example, FooImpl . The interface and the class need not be in the same Java package.

Registering an MXBean

Remember that you can write MXBeans only on the Java SE 6 platform. The J2SE 5.0 platform included some predefined MXBeans, but it did not allow you to add your own. If you have not already done so, download a snapshot from the JDK 6 Project site.

The code to register the MBean is exactly the same for a Standard MBean as for an MXBean:

    MBeanServer mbs =  
                  
    // or any other MBeanServer

    ObjectName name = new ObjectName("com.example:type=Memory");
    Object mbean = new Memory();
    mbs.registerMBean(mbean, name);
                
 
How Does an MXBean Work?

The key idea behind MXBeans is that types such as MemoryUsage that are referenced in the MXBean interface get mapped into a standard set of types, the so-called Open Types that are defined in the package javax.management.openmbean. The exact mapping rules appear in the MXBean specification. To oversimplify, one can say that simple types such as int or String are unchanged, whereas complex types such as MemoryUsage get mapped to the standard type CompositeDataSupport. This is why you see the MemoryUsage attribute as a CompositeDataSupport in jconsole Figure 4.

Run the following code to see the difference between a Standard MBean and an MXBean:

    MBeanServer mbs = ...
                  
something
...; ObjectName name = new ObjectName("com.example:type=Memory"); Object memoryUsage = mbs.getAttribute(name, "MemoryUsage"); System.out.println(memoryUsage.getClass());
 

For a Standard MBean, it will print

    class com.example.MemoryUsage
 

whereas for an MXBean, it will print

    class javax.management.openmbean.CompositeDataSupport
 

So the way to extract the used item from the MemoryUsage bundle will differ, depending on whether it is in a Standard MBean or an MXBean:

Link Link
 

As you can see, the MXBean case is somewhat messier because it involves the general-purpose interface CompositeData rather than the specific class MemoryUsage. But if you are writing code that is specifically supposed to interface with the Memory MBean, as opposed to a generic client such as jconsole, then you can construct a proxy. In that situation, the Standard MBean and MXBean cases are again very similar:

Link Link
 

Note that the name JMX.newMBeanProxy can be used in the Java SE 6 platform in preference to the name MBeanServerInvocationHandler.newProxyInstance from J2SE 5.0.

An MXBean proxy has the ability to reverse the mapping of the MXBean. The MXBean mapped from MemoryUsage to CompositeDataSupport, and the proxy maps back from this CompositeDataSupport to the original MemoryUsage value. That's why the code to access a Standard MBean through a proxy is essentially the same as for an MXBean.

One subtle detail: The MXBean framework needs some extra information in order to be able to reconstruct this MemoryUsage. The details are in the MXBean specification, but in short, you usually need to add a @ConstructorProperties annotation to a public constructor of the class that says how the parameters of the constructor match the properties. You usually need to do this only if you are accessing the MXBean through a proxy, but adding that annotation is always a good practice. For example:

public class MemoryUsage {
     
                  
@ConstructorProperties({"init", "used", "committed", "max"})
public MemoryUsage(long init, long used, long committed, long max) { this.init = init; ... } public long getInit() { return this.init; } public long getUsed() {...} public long getCommitted() {...} public long getMax() {...} }
 

For another example, see Bug ID 5082475, a request for enhancement to add new methods Method.getParameterNames() and Constructor.getParameterNames().

Note: Another change to the Java SE 6 platform version of this class is that MemoryUsage removes implements Serializable and the serialVersionUID definition because they are not necessary with MXBeans, though they are not harmful.

Handling Inter-MXBean References

MXBeans contain a facility, based on an idea by Charles Paclat of BEA, for managing references between MXBeans. For example, suppose you have some products, each of which is composed of one or more modules. You have one MBean per product and one MBean per module. Given the MBean for a product, you would like to be able to see the MBeans for its modules and vice versa, as shown in Figure 6.

Figure 6: Managing References Between MXBeans

The corresponding MXBean interfaces might look like this:

public interface ProductMXBean {
    public Set<ModuleMXBean> getModules();
    public void addModule(ModuleMXBean module);
    public void removeModule(ModuleMXBean module);
    public String getName();
    // ...
}

public interface ModuleMXBean {
    public ProductMXBean getProduct();
    public String getName();
    // ...
}
 

The ModuleMXBean has an attribute called Product, defined by the getProduct()method. The declared type of that attribute is ProductMXBean, but this type will be mapped by the MXBean framework. So the actual type that a client such as jconsole will see will, in fact, be ObjectName. If you connected jconsole to this application, you might see something that resembles Figure 7:

Figure 7: What ModuleMXBean looks like in jconsole

The Product attribute is an ObjectName, specifically the name of the ProductMXBean.

Creating References

What might the code that created the ProductMXBean and ModuleMXBeans look like? Here's one possibility:

        // Create and register the Product.
        ObjectName productName = 
            new ObjectName("com.example.myapp:type=Product");
        ProductMXBean product = new ProductImpl("wonderprod");
        mbeanServer.registerMBean(product, productName);
        
        // Create and register the Modules and 
        // add each one to the Product.
        String[] moduleIds = {"fred", "jim", "sheila"};
        for (String moduleId : moduleIds) {
            ModuleMXBean module = 
                 
                  
new ModuleImpl(moduleId, product)
; ObjectName moduleName = new ObjectName("com.example.myapp:type=Module," + "name=" + moduleId); mbeanServer.registerMBean(module, moduleName);
product.addModule(module)
; }
 

Because you are creating the Product and its Modules all at the same time, you are able to give the Product a direct reference to each of the Module objects, and vice versa. The code in bold illustrates this. Such an approach assumes an intimate relationship between all the objects in question, and the approach is most suitable if the code for creating all the MXBeans is small and centralized.

Here's another possibility. The bold type in the following code indicates changes or additions to the preceding code sample.

        // Create and register the Product.
        ObjectName productName = 
            new ObjectName("com.example.myapp:type=Product");
        ProductMXBean product = new ProductImpl("wonderprod");
        mbeanServer.registerMBean(product, productName);
         
                  
ProductMXBean productProxy =


JMX.newMXBeanProxy
(mbeanServer, productName,
ProductMXBean.class);
// Create and register the Modules and // add each one to the Product. String[] moduleIds = {"fred", "jim", "sheila"}; for (String moduleId : moduleIds) { ModuleMXBean module = new ModuleImpl(moduleId,
productProxy
); ObjectName moduleName = new ObjectName("com.example.myapp:type=Module,name=" + moduleId); mbeanServer.registerMBean(module, moduleName);
ModuleMXBean moduleProxy =
JMX.newMXBeanProxy(mbeanServer, moduleName, ModuleMXBean.class);
product.addModule(
moduleProxy
); }
 

The coupling between the Product and its Modules is much looser because each knows only the ObjectName of the other -- and not the object itself, which it knew before. The Modules and Product could have been created in separate places in the code and would have had to agree only on the ObjectNames.

Navigating the MXBean Hierarchy

The power of this reference-based approach is apparent when you are coding a client that interacts with this model.

You can construct a proxy for an MXBean using the new method JMX.newMXBeanProxy. If you have a proxy for a ModuleMXBean, for example, then it is an object of type ModuleMXBean. If you call its getName() method, this will result in a call to MBeanServerConnection.getAttribute(moduleObjectName, "Name") , which is very handy.

But the ModuleMXBean interface also has this method:

    ProductMXBean getProduct();
 

What does it return when you call it on a ModuleMXBean proxy? You might have guessed the answer: It returns another proxy, this time for the ProductMXBean.

Suppose you know the ObjectName of a ModuleMXBean and want to find the names of all the modules in the same product. You can do this by using a proxy for the ModuleMXBean to obtain a proxy for its ProductMXBean, as Figure 8 shows.

Figure 8: Using a Proxy for the ModuleMXBean to Obtain a Proxy for Its ProductMXBean

Then you can navigate from this proxy to proxies for all of the product's modules, as Figure 9 shows.

Figure 9: Navigating to Proxies for All of the Product's Modules

Here is the code to find the names of all the modules when you have only the ObjectName of one module:

    ModuleMXBean startModuleProxy =
        JMX.newMXBeanProxy(mbeanServerConnection, startModuleName,
                           ModuleMXBean.class);
    ProductMXBean containingProduct = startModuleProxy.getProduct();
    Set<ModuleMXBean> modules = containingProduct.getModules();
    for (ModuleMXBean module : modules)
        System.out.println(module.getName());
 

The ability to navigate through the MBean model using proxies is very powerful, and it provides a strong incentive to use this approach in managing references between MBeans.

Recursive Data Structures

MXBeans do not yet support recursive data structures. The solution is to add a level of indirection. For example, suppose you have a graph consisting of nodes. You might want to define this:

public class Node {
    @ConstructorProperties({"name", "adjacent"})
    public Node(String name, Set<Node> adjacent) {
        this.name = name;
        this.adjacent = adjacent;
    }
    public String getName() {
        return name;
    }
    public Set<Node> getAdjacent() {
        return adjacent;
    }
    private final Set<Node> adjacent;
}
 

Given MXBeans' lack of support for recursive data structures, this will not work. Why? The fundamental reason is that CompositeData is immutable, and cyclic data structures cannot be built from immutable elements.

Here's a solution:

public class Node {

    @ConstructorProperties({"name", "adjacent"})
    public Node(String name, Set<String> adjacent) {
        this.name = name;
        this.adjacent = adjacent;
    }
    ...
}
public class Graph {
    @ConstructorProperties({"nodeNames"})
    public Graph(Map<String, Node> nodeNames) {
        ...
    }
    ...
}
 

Here, the self-reference from the Node class has been moved out into a containing Graph class. Removing self-references in this way is always possible, though it may require work.

When Not to Write an MXBean

It's generally best to write an MXBean in the Java SE 6 platform wherever you would write a Standard MBean in the J2SE 5.0 platform. The only real differences between MXBeans and Standard MBeans are these:

  • The naming convention is different. A Standard MBean is a class such as com.example.Foo, which implements an interface such as com.example.FooMBean. An MXBean is a class with any name you like, which implements an interface such as com.example.FooMXBean.
  • The types that you use in an MXBean interface get mapped into a predefined set of types. Thus, there are constraints on what types you can use. This makes for a much clearer type system.

One of the constraints on types in an MXBean interface is that you cannot use inheritance. For example, you cannot use Object as a wildcard type to represent String sometimes and to represent Integer or Date at other times. If you know the complete set of possible types, you could do what Java Architecture for XML Binding (JAXB) does for an XML <choice>: define a set of properties, all but one of which are null.

The other reason to avoid MXBeans is if you are writing code that must run on J2SE 5.0 or earlier versions. That reason will diminish in importance over time as developers move to more current Java SE versions.

Conclusion

MXBeans provide a convenient way to bundle related values without requiring special client configurations to handle the bundles. On the whole, it's best for developers to write MXBeans in cases in which they have previously written Standard MBeans. MXBeans enhance the functionality of the JMX API in the Java SE 6 platform, which is slated for release this year.

For More Information

Éamonn McManus's Blog
Java Management Extensions (JMX)
JMX Articles
JSR 174: Monitoring and Management Specification for the Java Virtual Machine
Download JDK 6

About the Authors

Éamonn McManus is the technical lead of the JMX team at Sun Microsystems. He heads the technical work on JSR 255 (JMX API 2.0) and JSR 262 (Web Services Connector for JMX Agents). He previously worked at the Open Software Foundation's Research Institute on the Mach microkernel and countless other projects, including a TCP/IP stack written in the Java programming language. Visit his blog.

Janice J. Heiss, in addition to exploring the world of Java technology as a staff writer for Sun Microsystems Inc., is a published writer of poetry, fiction, and memoir and has written and performed for the stage, including stand-up comedy.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.