 |
What is an MXBean?
Posted by emcmanus on February 13, 2006 at 09:58 AM | Comments (5)
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:
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, and
committed will all be less than the value of
max.
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 using
getAttributes instead of
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 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 of
init, used, committed,
and max were itself a bundle of statistical
values, say min, current, max,
and average:
Would you want to define 16 attributes, such as
UsedMin, 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 calling
getAttribute. You can have more than one attribute
that returns the bundle type, for example
getHeapMemoryUsage and
getNonHeapMemoryUsage.
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:
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 MemoryUsage
approach 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 the
MemoryMBean into a MemoryMXBean:
That looks a bit cryptic, but in fact all we have to do is
double-click on the
javax.management.openmbean.CompositeDataSupport to get this:
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 MBean | MXBean |
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 MemoryMBean
interface has been renamed to MemoryMXBean!
Recall that, with a Standard MBean, you define an interface
called SomethingMBean and a class called
Something that implements it. So in the
Standard MBean example here we have an interface
MemoryMBean 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:
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, 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 the
MemoryUsage attribute as a
CompositeDataSupport 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 the
used item from the MemoryUsage bundle is
different depending on whether it's in a Standard MBean or an
MXBean:
| Standard MBean | MXBean |
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 interface
CompositeData rather than the specific class
MemoryUsage. 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 a proxy and then the Standard MBean and
MXBean cases are once again very similar:
| Standard MBean | MXBean |
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 unmemorable
MBeanServerInvocationHandler.newProxyInstance has
been supplemented in Mustang by the much nicer
JMX.newMBeanProxy.)
An MXBean proxy is able to reverse the mapping that the MXBean
did. So 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 is that the MXBean framework needs some extra
information in order to be able to reconstruct this
MemoryUsage. 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 the
serialVersionUID definition from the definition of
MemoryUsage, 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 like com.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
be String and other times Integer or
Date. 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.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Love hearing about new and existing JMX stuff. Keep 'em coming.
Posted by: kyle on February 13, 2006 at 10:58 AM
-
Eamonn, it's just a shame that the MXBean mechanism cannot (yet?) support recursive data structures like for example:
public static class ThrowableInfo
{
public Throwable getThrowable()
{
return new Throwable();
}
}
The recursivity here is that Throwable has a method
public Throwable getCause()
that returns a class of the same type of the declaring class.
Have you found a workaround for this problem ?
Posted by: simonebordet on February 19, 2006 at 11:48 AM
-
Hi Simone,
I was convinced the spec already talks about this, but it doesn't! We'll need to fix that.
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 {
@PropertyNames({"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;
}
But of course this won't work because MXBeans don't support recursive data structures. That in turn is because CompositeData is immutable and you can't build cyclic data structures from immutable elements.
So instead of the above, you can do the following:
public class Node {
@PropertyNames({"name", "adjacent"})
public Node(String name, Set<String> adjacent) {
this.name = name;
this.adjacent = adjacent;
}
...
}
public class Graph {
@PropertyNames({"nodeNames"})
public Graph(Map<String, Node> nodeNames) {
...
}
...
}
In the specific case of Throwable, and assuming the usual case where following getCause() eventually reaches null (there are no cycles), you can reasonably represent it as a List<ThrowableInfo> or ThrowableInfo[], where ThrowableInfo contains at least a String that is the exception class, a String that is the detail message, and a StackTraceElement[].
I've submitted bug 6388374 to track this, which should be visible on bugs.sun.com starting tomorrow.
Posted by: emcmanus on February 21, 2006 at 09:10 AM
-
Hope you're still checking comments on this old blog entry!
Anway, I'm trying to use MXBeans to update some related entries in a transactional manner. I have an mxbean that has a read/write attribute "myProp" of type "myType". myType has read/write String properties.
The attribute is published and appears in jconsole just fine, but the properties of myType are not editable. Is there something that I need to do? Is this a limitation of jmx? or of jconsole?
Thanks.
Posted by: n0_j0 on July 11, 2007 at 01:59 PM
-
n0_j0, this is a limitation of JConsole. A Request For Enhancement is open against it (6441497) but there are no definite plans to fix it in the near future.
You could write a JConsole plugin that would allow you to edit your values. This would appear as a separate tab in JConsole. A good example is in Luis-Miguel Alventosa's blog.
Alternatively, since JConsole is open-source, you could modify the existing MBeans tab to allow editing of CompositeData values. This is especially easy if you use NetBeans: see the page on the NetBeans site about building JConsole. It shouldn't be much harder with a different IDE. We'd certainly be interested if you go this route - let the jmx-dev mailing list know!
Posted by: emcmanus on July 20, 2007 at 03:07 AM
|