Skip navigation

The Tiger JDK introduced a nifty feature whereby you could run an application with -Dcom.sun.management.jmxremote and then later connect to it using the jconsole tool. That allows you to see the JVM's instrumentation (memory, classes, threads) plus any JMX instrumentation you've defined yourself. As Alan Bateman describes, with the Mustang JDK you can now attach jconsole to any application, even if it wasn't launched with the magic -Dcom.sun.management.jmxremote incantation.

This is excellent news, for a few reasons. First of all, what a bummer if you suddenly realize after your uninterruptible app has been running for five days that you really want to get at its instrumentation, but damn, you forgot to incant-Dcom.sun.management.jmxremote! Not a problem any more. You can connect to any of your JVM processes from a jconsole on the same machine. (You can't connect to anyone else's processes, and you can't connect remotely in this way.)

Secondly, there's a small penalty in start-up time and in footprint when you launch with the incantation. On the old slow workstation I'm typing this on, this is almost three seconds and 500K. This isn't enormous, but it's not negligible either. So why not wait to pay the penalty until you actually need to, which might be never?

And finally, enabling this connection, even from the local machine, means creating and listening on a TCP/IP socket. If you're not going to be using it then why bother?

So the new feature is a big plus! If you want to play with it, you can download the latest Mustang snapshot at http://download.java.net/jdk6/binaries/. Both jconsole and the target app must be on Mustang.

By the way, Alan modestly doesn't mention the deep technical wizardry he needed to do to get this to work. Causing a target process to execute code for you (to create the JMX connector server) isn't a piece of cake!

In a comment on my last entry, rgreig asks:

One thing that struck me about MXBeans is that they are really fusion of standard MBeans and OpenMBeans, since they only use standard types. However they don't actually conform to OpenMBean conventions.

Why was the decision taken not to make MXBeans OpenMBeans? (i.e. return an OpenMBeanInfo from getMBeanInfo())

Therein lies a tale!

MXBeans really are OpenMBeans. But as you note they don't return OpenMBeanInfo. However, in Mustang, all the same information that would have been in the OpenMBeanInfo is present via Descriptors.

Instead of...

MBeanInfo mbi = mbeanServer.getMBeanInfo(objectName);
MBeanAttributeInfo mbai = mbi.getAttributes()[0];
OpenMBeanAttributeInfo ombai = (OpenMBeanAttributeInfo) mbai;
OpenType ot = ombai.getOpenType();

...you can use...

MBeanInfo mbi = mbeanServer.getMBeanInfo(objectName);
MBeanAttributeInfo mbai = mbi.getAttributes()[0];
Descriptor d = mbai.getDescriptor();
OpenType ot = (OpenType) d.getFieldValue("openType");

This fusion of OpenMBeanInfo functionality into plain MBeanInfo means that you can have OpenModelMBeans and other strange beasts.

Nevertheless, we did originally plan for MXBeans to return OpenMBeanInfo. The reason they don't is subtle.

In the original incarnation of MXBeans, in java.lang.management, we wanted to use the typelong[] in the ThreadMXBean. But OpenTypes didn't support arrays of primitive types - they would only have supported Long[], substantially less efficient. So we avoided using OpenMBeanInfo in Tiger with the intent of fixing this problem in Mustang.

It now is possible to have an array of primitive type as an OpenType, so we could now describe the ThreadMXBean with an OpenMBeanInfo. However, there are some interoperability concerns that we didn't foresee.

Suppose you have a Tiger client, say jconsole running on Tiger, talking to a Mustang server. Sincelong[] didn't exist as an OpenType in Tiger, if we sent an OpenMBeanParameterInfo with typelong[] to a Tiger, it will see it as if its type wereLong[]. Suppose it lets the user call a method like
ThreadMXBean.getThreadInfo(long[] ids)

JConsole proposing ThreadMXBean operations 

If jconsole saw an OpenMBeanParameterInfo for aLong[] then it would expect the signature array in MBeanServerConnection.invoke to containLong[].class.getName(), i.e."[Ljava.lang.Long;". But since the parameter is really a long[], it should belong[].class.getName(), i.e. "[J". If you pass the wrong signature, the method invocation will fail.

We could have envisaged various hacks to work around this problem, but we decided it would be simpler to continue to useMBeanParameterInfo rather thanOpenMBeanParameterInfo, with the Descriptor containing the OpenType. In the example at hand, we'll have anMBeanParameterInfo wheregetDescriptor().getFieldValue("openType") returnsArrayType.getPrimitiveArrayType(long[].class). On Tiger, there's no such OpenType, but there's no Descriptor inMBeanParameterInfo either so the inconsistency evaporates. The Tiger client sees just the MBeanParameterInfo.getType(), which is"[J" as required.

For what it's worth, our implementation does use anOpenMBeanParameterInfo when it doesn't contradict the constraints I mentioned. In particular, if your attribute or parameter is a CompositeData then even a Tiger client will be able to see the corresponding CompositeTypeusing OpenMBeanParameterInfo.getOpenType().

Summer is of course the time when people take their vacation, and nowhere more so than here in France. You could be forgiven for thinking that the entire country grinds to a halt between the traditional vacation boundaries of the 14th of July (Bastille Day) and the 15th of August (Feast of the Assumption, a public holiday, don't ask). It seems like three quarters of high-street shops are closed for business. More to the point, three quarters of my co-workers are also on vacation, so, if I'm not, then the amount of time I spend in discussion and co-ordination is drastically reduced.

This summer I had a period of about two weeks like that where I could just lower my head and code. And that was a good thing, because there was something that really needed to be done.

The MBean Server is the core of the JMX API, and its implementation in the JDK includes code that dates from the very earliest release of that API, and indeed its proprietary predecessors. That means it's had six years or more to accumulate bug-fixes, interesting ideas that didn't quite work out, new features that were grafted on as best we could, and so on.

The last straw for me was the addition of MXBeans to the JMX API. MXBeans were initially implemented as a standalone package that used only the public JMX API. When the MXBean feature was added to Mustang, we just did the minimal adaptation work to make it work. It was still operating as a standalone package, taking no advantage of the implementation classes in the rest of the JMX API. The idea was to get the feature in place quickly, then revisit it later to clean up.

If you're like me, you're extremely sceptical about things that "we'll clean up later". Somehow, if it works, it never does get cleaned up. There's always something more urgent to do. I could see this coming a mile off for MXBeans.

This was bad for a number of reasons. MXBeans and Standard MBeans have a great deal in common. In fact you could consider that Standard MBeans are just MXBeans where the type mapping is the identity. So the fact that they shared almost no code meant that there was a lot of functionality implemented twice. Bug-fixes applied to one wouldn't automatically apply to the other. New features, such as annotations for descriptor contents would have to be implemented separately for each one. And optimizations in one wouldn't appear in the other.

This was exactly the situation we were in during the French shutdown. Standard MBeans had lots of caching logic so that the same MBeanInfo instance could be shared between all MBeans of the same type, and only computed for the first of them. MXBeans didn't have that, but they did cache Method objects so they didn't have to be retrieved again for every getAttribute or invoke on the MBean.

So, out with the hatchet. Refactor the logic for Standard MBeans and MXBeans so that they are just two concrete subclasses of an abstract MBean class. Put the caching of MBeanInfo objects and Methods into this class. Use the Visitor Pattern so the same introspection logic can construct MBeanInfo, method cache, and proxy data.

In this sort of environment, you have to pay lots of attention to WeakReferences so that all the cached information goes away when it's no longer used. It's really easy to set things up so that if you register an MBean from some class loader and then unregister it again, the class loader object is still referenced and can never be garbage collected.

While I had the hatchet out, I changed the way MBeans are handled so that every MBean is ultimately a DynamicMBean. Standard MBeans and MXBeans get wrapped in a DynamicMBean. The previous logic had separate handlers for Dynamic MBeans and Standard MBeans, and that tended to produce gratuitous inconsistencies between the two.

The intent of all these changes is to simplify the code, so future evolution will be easier; to reduce per-MBean memory usage; and to speed up MBean operations. The first of these is hard to measure but the measurements I've made on the other two are very encouraging. These changes will appear in build 53 of Mustang, which should be available on java.net on about the 23rd of September.