Skip navigation

As you'll no doubt have read elsewhere, the Mustang Beta Release is now available. There are plenty of improvements in plenty of areas, including the JMX API.

The improvements to the JMX API are incremental changes to existing functionality, rather than entirely new features. The new features will arrive in Dolphin (Java SE 7), as defined by JSR 255. (The members of the JSR 255 Expert Group were also closely involved in the JMX API changes made in Mustang.)

We don't expect there to be any significant changes to the JMX API beyond the Beta. If you felt that the weekly Mustang snapshots from mustang.dev.java.netmight be too risky or too volatile, but you're tempted by the new features, now you can download the Beta and play with them. We think the Beta is pretty solid, but let us know through bugs.sun.comif you find a problem. In particular, if you find aregression (code that used to work on Tiger but doesn't work on Mustang), then you might be eligible for the Mustang regression challenge.

What's changed?

The two biggest changes to the JMX API in Mustang concernMXBeans and Descriptors.

I wrote about MXBeans recently. MXBeans provide a way to package related information together in an MBean without requiring any special configuration for clients that interact with that MBean. This is an incremental change, because MXBeans already existed in J2SE 5.0, in the packagejava.lang.management. What's new is that users can now define their own MXBeans, in addition to the standard set defined in java.lang.management.

I also wrote about Descriptors a while back. Descriptors give you a convenient way to attach arbitrary extra metadata to your MBeans. This is an incremental change because Descriptors have always existed in the JMX API, but only in conjunction with Model MBeans. In Mustang, Descriptors are available with all types of MBeans. This helps to erase the somewhat arbitrary distinctions between different sorts of MBeans, notably Open MBeans and Model MBeans.

Here are a few of the other improvements that Mustang brings to the JMX API.

Generification

For obscure compatibility reasons, we weren't able to generify the JMX API in Tiger. This has been fixed in Mustang. So MBeanServer.queryNames now returns aSet<ObjectName> rather than a plainSet. ObjectName.geyKeyPropertyList() returns aHashtable<String,String> rather than a plainHashtable (compatibility forbids us from changing it to Map<String,String>, alas). And, especially, the methods in the RelationServiceMBean are much more understandable with their new generic return types. It was pretty tough understanding what was in the Map returned by RelationServiceMBean.findAssociatedMBeans. Now that it's a Map<ObjectName,List<String>>you have a fighting chance of making sense of it.

One nice effect of generification is that you no longer need a cast in the following assignment:

SomeMBean proxy = (SomeMBean)
    MBeanServerInvocationHandler.newProxyInstance(
        mbeanServer, objectName, SomeMBean.class, false);

As a side-note, this can now be replaced by the much easier-to-remember:

SomeMBean proxy =
    JMX.newMBeanProxy(mbeanServer, objectName, SomeMBean.class);

More powerful wildcards in ObjectName

The ObjectName class has always had support for wildcards, to define ObjectName patterns. But you could not use a wildcard within the value of a key. For example, you could not write domain:type=Dir,path="/foo/bar/*"to match an ObjectName such asdomain:type=Dir,path="/foo/bar/baz" but notdomain:type=Dir,path="/fred/jim/sheila". Now you can.

ObjectName implements Comparable<ObjectName>

ObjectName is now Comparable. The ordering function is a little arbitrary, but the important point here is that you can take a collection of ObjectNames, such as the result of MBeanServer.queryNames, and put them in a SortedSet to see them in a more meaningful order. For example, if I write this...

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> names = mbs.queryNames(null, null);
System.out.println(names.toString().replace(", ",
                        System.getProperty("line.separator")));

...then I'll get this rather random output:

[java.lang:type=MemoryManager,name=CodeCacheManager
java.lang:type=Compilation
java.lang:type=GarbageCollector,name=Copy
com.sun.management:type=HotSpotDiagnostic
java.lang:type=MemoryPool,name=Eden Space
java.lang:type=Runtime
java.lang:type=ClassLoading
java.lang:type=MemoryPool,name=Survivor Space
java.lang:type=Threading
java.lang:type=GarbageCollector,name=MarkSweepCompact
java.util.logging:type=Logging
java.lang:type=Memory
java.lang:type=OperatingSystem
java.lang:type=MemoryPool,name=Code Cache
java.lang:type=MemoryPool,name=Tenured Gen
java.lang:type=MemoryPool,name=Perm Gen
JMImplementation:type=MBeanServerDelegate]

On the other hand, if I add the following line after the assignment to names...

names = new TreeSet(names);  // a TreeSet is a SortedSet

...then I get this reasonable ordering:

[JMImplementation:type=MBeanServerDelegate
com.sun.management:type=HotSpotDiagnostic
java.lang:type=ClassLoading
java.lang:type=Compilation
java.lang:type=GarbageCollector,name=Copy
java.lang:type=GarbageCollector,name=MarkSweepCompact
java.lang:type=Memory
java.lang:type=MemoryManager,name=CodeCacheManager
java.lang:type=MemoryPool,name=Code Cache
java.lang:type=MemoryPool,name=Eden Space
java.lang:type=MemoryPool,name=Perm Gen
java.lang:type=MemoryPool,name=Survivor Space
java.lang:type=MemoryPool,name=Tenured Gen
java.lang:type=OperatingSystem
java.lang:type=Runtime
java.lang:type=Threading
java.util.logging:type=Logging]

Notifications

NotificationBroadcasterSupport constructor with MBeanInfo[]

If you have an MBean that emits notifications, it must implement the NotificationBroadcaster interface. The JMX API provides a default implementation called NotificationBroadcasterSupport. But you are supposed to override the getNotificationInfo method to return an array of all the notification types you might send. Since this array is almost always a constant,NotificationBroadcasterSupport now has a constructor that allows you to supply the array and avoid having to override getNotificationInfo().

New StandardEmitterMBean class

Speaking of MBeans that emit notifications, if you're writing a Standard MBean but you can't subclassNotificationBroadcasterSupport because you have already need to inherit from a different parent class, you can use the new StandardEmitterMBean class to build a notification-emitting MBean from your Standard MBean class. You can also use this class if you need to customize the behaviour of a notification-emitting Standard MBean, for example to change the contents of its MBeanInfo.

New Query.isInstanceOf

You can now query for MBeans that are an instance of a particular class or interface. For example, to find all Monitor MBeans in the domesne domain, you could write:

QueryExp isMonitor =
    Query.isInstanceOf("javax.management.monitor.MonitorMBean");
Set<ObjectName> monitorNames =
    mbeanServer.queryNames(new ObjectName("domesne:*"), isMonitor);

Actually, I should warn you that this will change after the Beta, and the first line will become this instead:

QueryExp isMonitor =
    Query.isInstanceOf(Query.value("javax.management.monitor.MonitorMBean"));

Alert readers will deduce that either I was lying when I said there would be no significant API changes after the Beta, or I don't consider this a significant change. Either way, this is likely to be about as significant as it will get.

Monitor Service supports complex types

Previously, the Monitor Service defined by javax.management.monitor could only monitor attributes of simple numeric types like int ordouble, or strings. (A reminder that this service allows you to sample an attribute periodically and send a notification if it goes over or under a certain threshold.) Now, you can monitor a value inside a complex type using a syntax likeconnectionStats.open.current.

The ability to pick out values like this is confined to the Monitor Service. You cannot callmbeanServer.getAttribute("connectionStats.open.current")to obtain a similar effect. (We thought about allowing that, but it could pose problems for existing code that usesattribute.names.with.dots and it's also not obvious what the corresponding behaviour for setAttributeshould be.)

Address of a JMXConnector

If you have an RMI connector client obtained with code something like this...

JMXServiceURL jurl =
    new JMXServiceURL("service:jmx:rmi://blah);
JMXConnector jc = JMXConnectorFactory.connect(jurl);

...then you can now retrieve the address from jclike this...

JMXServiceURL jurl2 = ((JMXAddressable) jc).getAddress();

Here, jurl2 will be equal to jurl.

So there it is

As usual, if you have comments on these new features or the way they are specified, feel free to drop me a line at jmx-spec-comments@sun.com.

One of the important new features of the JMX API in Mustang (Java SE 6) is the ability to create MXBeans. MXBeans provide a convenient way to bundle related values together without requiring clients to be specially configured to handle the bundles. Here's the complete story about MXBeans.

Bundles of values

When you are designing an MBean, you sometimes want to have a bundle of associated values. For example, you might have a bundle that represents a snapshot of memory usage. It might look something like this:

A MemoryUsage is a bundle of named values 

Typically a snapshot will have some implicit constraints based on the meanings of the values. In this case, for instance, the values of init, used, andcommitted will all be less than the value ofmax.

Each value as a separate attribute

One way you might model this is to have each value be an attribute of your MBean, like this:

   
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();
    ...
}

The disadvantage of this approach is that you could potentially get an inconsistent set of values. For example, if your client code looks like this...

   
    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();

...then you might get a value of used that is greater than the value of max, even though that makes no sense. If you went back and got max again, the new value would indeed be greater than the used value.

You might think you could work around this problem by usinggetAttributes instead ofgetAttribute:

   
    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 considerably nastier, and you don't have the option of making a proxy. It also doesn't solve the problem! Ultimately, the MBean Server will handle the getAttributes call by calling your MBean's getMax() method and then calling your MBean's getUsed() method. The interval between these calls might be smaller, but it's still possible for the values to be inconsistent.

You could solve this problem by making your MBean be a Dynamic MBean rather than a Standard MBean, and implementing DynamicMBean.getAttributes so that it does return a consistent set of attributes. That's a lot of work, though.

Furthermore, this idea of breaking a complex set of values out into separate attributes can quickly become unmanageable. Suppose that, instead of being plain long values, each ofinit, used, committed, andmax were itself a bundle of statistical values, saymin, current, max, andaverage:

The values in a MemoryUsage might themselves be complex 

Would you want to define 16 attributes, such asUsedMin, UsedCurrent,CommittedMin?

Defining a type for the bundle of values

An alternative approach is to define a Java class that represents the bundle of values you want to deal with, for example like this:

   
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();
    ...
}

Now your client code might look something like this:

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

This looks much nicer. The fact that the four values are related is explicitly reflected in the MBean's interface, and it's no longer possible for you to get an inconsistent set of values inadvertently. You can make a proxy rather than callinggetAttribute. You can have more than one attribute that returns the bundle type, for examplegetHeapMemoryUsage andgetNonHeapMemoryUsage.

But there's a subtle problem with this. For the client code to compile and run, we now need to supply it with a copy of the compiled MemoryUsage class. Suppose the client is a generic one like jconsole. If we connect jconsole to our MemoryMBean then by default we'll see something like this:

jconsole showing unavailable class MemoryUsage 

The MemoryUsage attribute is shown as Unavailable because jconsole doesn't know the class MemoryUsage. When it tries to retrieve the value of the attribute, it gets a ClassNotFoundException.

You can launch jconsole with a classpath that includes the MemoryUsage class. But this isn't a very good solution. JConsole can connect to any number of remote MBean Servers. Imagine if you had to figure out all the classes used by any of those MBean Servers and put them all in jconsole's classpath. Imagine if furthermore some of the MBean Servers used the same class but with different versions. This quickly gets messy.

Aside: Code downloading. A solution that is sometimes proposed to the classpath problems just mentioned is RMI code downloading. Jini technology uses this heavily, for example. The idea here would be that if jconsole can't find a class like MemoryUsage, it can download it from an HTTP server associated with the remote MBean Server. This is a nice idea if you can make it work. But you'll need to arrange for your HTTP server to have the right classes, and keep them in sync with the MBean Server, and configure any firewalls so that the HTTP connection is allowed through, and set up security in jconsole so that it doesn't allow the downloaded classes to do things they shouldn't, and use the RMI connector rather than any other connector. In a sense this is the ticket price for entering the Jini universe. Once you're in, there's 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

MXBeans are an extension of the MemoryUsageapproach we just saw, but designed in a way that avoids the problem with classes. MXBeans were originally designed (by Mandy Chung, Sanjay Radia, and me) as part of the work for JSR 174, "Monitoring and Management Specification for the Java Virtual Machine". As such, you can see the use of MXBeans by looking at the package java.lang.management in J2SE 5.0 or later. In particular, the MemoryUsage example above is inspired by the MemoryUsage class from that package, and the MemoryMXBean interface that uses it.

Let's see how jconsole will work if we change theMemoryMBean into a MemoryMXBean:

JConsole showing MemoryUsage as a javax.management.openmbean.CompositeDataSupport 

That looks a bit cryptic, but in fact all we have to do is double-click on the javax.management.openmbean.CompositeDataSupportto get this:

JConsole showing MemoryUsage as CompositeData items 

The bundle of information we're interested in is there, and we didn't have to do any special configuration of jconsole to see it!

How do I write an MXBean?

To get this effect, we changed the Memory MBean from a Standard MBean to an MXBean. This involves almost no modifications:

         
Standard MBeanMXBean
public interface MemoryMBean {
    public MemoryUsage getUsage();
}
public class Memory implements MemoryMBean {
    public MemoryUsage getMemoryUsage() {
        return memoryUsageSnapshot();
    }
    private static native MemoryUsage
            memoryUsageSnapshot();
    ...
}
public interface MemoryMXBean {
    public MemoryUsage getUsage();
}
public class Memory implements MemoryMXBean {
    public MemoryUsage getMemoryUsage() {
        return memoryUsageSnapshot();
    }
    private static native MemoryUsage
            memoryUsageSnapshot();
    ...
}

The only change is that the MemoryMBeaninterface has been renamed to MemoryMXBean!

Recall that, with a Standard MBean, you define an interface called SomethingMBean and a class calledSomething that implements it. So in the Standard MBean example here we have an interfaceMemoryMBean and a class Memory that implements it.

With MXBeans, the convention is that the interface must be called SomethingMXBean. The class can be called Something as here, or in fact anything else. I usually use SomethingImpl. There is no requirement that the interface and the class be in the same Java package.

Registering an MXBean

Remember that you can only write MXBeans on Java SE 6 (Mustang). If you haven't already done so, you might want to download a snapshot from mustang.dev.java.net.

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

   
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    // 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 asMemoryUsage 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, but to oversimplify we could say that simple types like int or String are unchanged, while complex types like MemoryUsage get mapped to the standard type CompositeDataSupport. This is why we saw theMemoryUsage attribute as aCompositeDataSupport in jconsole above.

So if you run the following code you'll see the difference between a Standard MBean and an MXBean:

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

For a Standard MBean, this will print:
class com.example.MemoryUsage
whereas for an MXBean, it will print:
class javax.management.openmbean.CompositeDataSupport

So this means that the way you would extract theused item from the MemoryUsage bundle is different depending on whether it's in a Standard MBean or an MXBean:

         
Standard MBeanMXBean
    MBeanServer mbs = ...whatever...;
    ObjectName name =
        new ObjectName("com.example:type=Memory");
    MemoryUsage memoryUsage = (MemoryUsage)
        mbs.getAttribute(name, "MemoryUsage");
    long used = memoryUsage.getUsed();
    MBeanServer mbs = ...whatever...;
    ObjectName name =
        new ObjectName("com.example:type=Memory");
    CompositeData memoryUsage = (CompositeData)
        mbs.getAttribute(name, "MemoryUsage");
    long used = (Long) memoryUsage.get("Used");

Obviously this is a bit messier in the MXBean case, because we're dealing with the general-purpose interfaceCompositeData rather than the specific classMemoryUsage. But in fact if we are writing code that is specifically supposed to interface with the Memory MBean, as opposed to a generic client like jconsole, then we can construct aproxy and then the Standard MBean and MXBean cases are once again very similar:

         
Standard MBeanMXBean
    MBeanServer mbs = ...whatever...;
    ObjectName name =
        new ObjectName("com.example:type=Memory");
    MemoryMBean proxy =
        JMX.newMBeanProxy(mbs, name, MemoryMBean.class);
    MemoryUsage memoryUsage = proxy.getMemoryUsage();
    long used = memoryUsage.getUsed();
    MBeanServer mbs = ...whatever...;
    ObjectName name =
        new ObjectName("com.example:type=Memory");
    MemoryMXBean proxy =
        JMX.newMXBeanProxy(mbs, name, MemoryMXBean.class);
    MemoryUsage memoryUsage = proxy.getMemoryUsage();
    long used = memoryUsage.getUsed();

(Notice in passing that the hideously unmemorableMBeanServerInvocationHandler.newProxyInstance has been supplemented in Mustang by the much nicerJMX.newMBeanProxy.)

An MXBean proxy is able to reverse the mapping that the MXBean did. So the MXBean mapped from MemoryUsage toCompositeDataSupport, and the proxy maps back from this CompositeDataSupport to the originalMemoryUsage 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 is that the MXBean framework needs some extra information in order to be able to reconstruct thisMemoryUsage. The gory details are in the MXBean spec, but the short summary is that you usually need to add an annotation to a public constructor of the class, that says how the parameters of the constructor match the properties. You usually only need to do this if you are accessing the MXBean through a proxy, but it's good practice to do it always. 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() {...}
}

(I've also removed implements Serializable and theserialVersionUID definition from the definition ofMemoryUsage, because they're not necessary with MXBeans, though they do no harm.)

So when would I not write an MXBean?

Going forward, it will generally be a good idea to write an MXBean wherever you would write a Standard MBean today. The only real differences between MXBeans and Standard MBeans are these:

  • The naming convention is different. A Standard MBean is a class like com.example.Foo, that implements an interface like com.example.FooMBean. An MXBean is a class with any name you like, that implements an interface likecom.example.FooMXBean.
  • The types that you use in an MXBean interface get mapped into a predefined set of types. A consequence of this is that there are constraints on what types you can use. In my opinion this is a good thing because it makes for a much clearer type system.

In particular, one of the constraints on types in an MXBean interface is that you can't use inheritance. For example, you can't use Object as a wildcard type, that might sometimes beString and other times Integer orDate. If you know the complete set of possible types, you could do what JAXB does for an XML <choice>, namely define a set of properties, all but one of which are null.

Other than possible problems with inheritance, the other significant reason you might have for avoiding MXBeans is if your code needs to run on J2SE 5.0 or earlier. Obviously that reason will be less and less important as time goes on!

Update: see also my later blog entry on Inter-MXBean References.