Skip to main content

Notes on unspecified behaviour, with a contentious aside about astronomy

Posted by emcmanus on November 10, 2006 at 8:09 AM PST

An inadvertent change in JDK 6 means that MBean attributes and
operations no longer appear in the order they were in a Standard
MBean interface. I wanted to fix this, but now I'm not so sure.

Here's the background. Suppose you have a Standard MBean with
this interface:

public interface PlanetMBean {
    public int getMercury();

    public String getVenus();
    public void setVenus(String x);

    public BigInteger getEarth();
    public void setEarth(BigInteger x);

    public boolean isMars();

    public double getJupiter();

    public byte getSaturn();

    public short getUranus();
    public void setUranus(short x);

    public long getNeptune();

    public void neptune();
    public void uranus(int x);
    public int saturn(int x, int y);
    public short jupiter(int x, long y, double z);
    public void mars(boolean x);
    public BigInteger earth();
    public String venus();
    public int mercury();
}

As you can see, it defines a set of attributes and operations
with random types, but names in a very definite and familiar
order.

If we create a Standard MBean like this, using JDK 5.0, then it
will look like this in JConsole.

alt="Attributes and operations appear in the same order as in the interface"
src="http://weblogs.java.net/blog/emcmanus/archive/jdk5-mbean-order.png"
width="570" height="501" />

However, if we run the same program on JDK 6, then it looks
like this:

alt="Attributes and operations appear in alphabetical order"
src="http://weblogs.java.net/blog/emcmanus/archive/jdk6-mbean-order.png"
width="570" height="524" />

Observe that the planets are no longer in astronomical order,
but in alphabetical order.

Aside: what about Pluto? You might have noticed that
I omitted Pluto from my list. That's because Pluto is not a
Planet
. A rare victory in the ongoing and hopeless war
against Stupidism was won earlier this year when the href="http://en.wikipedia.org/wiki/International_Astronomical_Union">International
Astronomical Union voted to recognize logic and common sense
and ignore inertia, nostalgia, and
welearneditinschoolsoitmustbetrueforeveria. More details than
anyone could possibly want to know are in href="http://en.wikipedia.org/wiki/2006_definition_of_planet">Wikipedia.
Beyond just astronomical reality, computer scientists will
surely be happier that there are eight planets, divided into
four big ones and four small ones, and with each group of four
further divisible into two big and two small. Or maybe that's
just me.

It turns out that this reordering is an unintended side-effect
of my href="http://weblogs.java.net/blog/emcmanus/archive/2005/09/optimizing_the.html">rewrite
of the MBean Server internals. I had intended to preserve the
order from the MBean interface, but in a moment of distraction I
used a href="http://download.java.net/jdk6/doc/api/java/util/TreeMap.html">TreeMap
instead of a href="http://download.java.net/jdk6/doc/api/java/util/LinkedHashMap.html">LinkedHashMap.

Nothing in the JMX specification requires that attributes and
operations in a Standard MBean appear in the same order in the href="http://download.java.net/jdk6/doc/api/javax/management/MBeanInfo.html">MBeanInfo
as they do in the MBean interface, so none of our automated
tests check that. We only discovered it recently, as a
side-effect of some unrelated tests being coded by one of our
Quality Engineers, Sandra Lions. Sandra logged href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6486655">bug
6486655 to track this.

This looks like something we would obviously want to fix,
right? Perhaps you thought carefully about the order you want
to present your attributes in, and followed that order in your
MBean interface. Perhaps you want to group related operations
together, for example addThingy() followed
immediately by removeThingy() (example due to href="http://blogs.sun.com/jmxetc/">Daniel Fuchs).

So, since I broke it, I started the process of fixing it. The
fix itself is completely trivial - just replace two
TreeMaps with LinkedHashMaps. But then
things got interesting.

The JDK has a clearly-defined engineering process. One rule in
that process is that every time we fix a bug, there must be a
regression test that fails without the fix and passes with the
fix.

So I duly coded a regression test. It was pretty much what you
would expect. In outline:

public class MethodOrderTest {
    public static interface PlanetMBean {
    ...same as above...
    }

    private static final String[] planets = {
    "mercury", "venus", "earth", "mars", "jupiter", "saturn",
    "uranus", "neptune"
    };

    public static void main(String[] args) throws Exception {
    ...create a Standard MBean using the PlanetMBean interface...
    MBeanInfo mbi = ...the MBeanInfo of this MBean...;
    MBeanAttributeInfo[] mbais = mbi.getAttributes();
    ...check that mbais[] is in the same order as planets[]...
    MBeanOperationInfo[] mbois = mbi.getOperations();
    ...check that mbois[] is in the opposite order as planets[]...
    }
}

After fixing the usual stupid mistakes in the test, the
attribute check passed. But the operation check failed! What
was different about operations? I went back and looked at the
introspection code but could see nothing.

What's more, the order the operations were appearing in was
surprising. I would have understood if they were in
alphabetical order. That would have meant that something else
besides the TreeMaps I had removed was causing them
to be sorted.

But the operations were in astronomical order, from Mercury to
Neptune, even though the interface declared them in reverse
astronomical order, from Neptune to Mercury. What could be
reversing them?

I won't get into the details of the painstaking investigation
that followed. Suffice it to say that I ended up boiling the
test failure down to this program:

public class MethodOrderTest {
    public static interface PlanetMBean {
    ...same as above...
    }

    private static final String[] planets = {
    "mercury", "venus", "earth", "mars", "jupiter", "saturn",
    "uranus", "neptune"
    };

    public static void main(String[] args) throws Exception {
    System.out.println(Arrays.asList(PlanetMBean.class.getMethods()));
    }
}

Notice that there's no JMX stuff at all here. It's just a call
to the Reflection API. And it shows the methods in the following
order:

[public abstract int MethodOrderTest$PlanetMBean.mercury(),
public abstract java.lang.String MethodOrderTest$PlanetMBean.venus(),
public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.earth(),
public abstract void MethodOrderTest$PlanetMBean.mars(boolean),
public abstract short MethodOrderTest$PlanetMBean.jupiter(int,long,double),
public abstract int MethodOrderTest$PlanetMBean.saturn(int,int),
public abstract void MethodOrderTest$PlanetMBean.uranus(int),
public abstract void MethodOrderTest$PlanetMBean.neptune(),
public abstract int MethodOrderTest$PlanetMBean.getMercury(),
public abstract java.lang.String MethodOrderTest$PlanetMBean.getVenus(),
public abstract void MethodOrderTest$PlanetMBean.setVenus(java.lang.String),
public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.getEarth(),
public abstract void MethodOrderTest$PlanetMBean.setEarth(java.math.BigInteger),
public abstract boolean MethodOrderTest$PlanetMBean.isMars(),
public abstract double MethodOrderTest$PlanetMBean.getJupiter(),
public abstract byte MethodOrderTest$PlanetMBean.getSaturn(),
public abstract short MethodOrderTest$PlanetMBean.getUranus(),
public abstract void MethodOrderTest$PlanetMBean.setUranus(short),
public abstract long MethodOrderTest$PlanetMBean.getNeptune()]

OK. So the getters and setters appear in the same order as in
the interface, but the other methods have been reordered and
appear earlier. Does reflection treat getters and setters
differently from other methods? What on earth...what the devil
is going on?

The next, surprising step is to comment out the definition of
the planets[] array. Lo and behold, the methods are
now printed out in the same order as they appear in the
interface!

Further investigation shows that if I include just the strings
"mercury", "venus", "earth"
in the planets[] array, then the three methods
mercury, venus, earth
appear first in the list, and the remaining methods appear in the
same order as in the interface.

If we go back and look at the documentation for href="http://download.java.net/jdk6/doc/api/java/lang/Class.html#getMethods()">Class.getMethods(),
we learn that "the elements in the array returned are not sorted
and are not in any particular order". I had read that text long
ago, but I had also observed that in practice the
elements were in the order that the methods were declared in the
class. So I wasn't too uneasy about assuming they would
be.

But the investigation above shows that this assumption is
false. In fact, the order of methods is the order in which the
JVM first encountered the method name strings
. If your
interface has methods called mercury and
venus, then that will often be the first time the JVM
is seeing those strings, especially in small test programs trying
to determine how Class.getMethods() behaves. So the
Method[] array will have the methods in the same
order as in the interface. But if you have previously loaded
another class that also has those method names, or even that
happens to contain those names as string constants, then the order
will be determined by that other class and could be completely
different!

The implications are wide-ranging. In particular, href="http://www.junit.org/">JUnit runs the various
testFoo() methods in a class in the order
that they show up in Class.getMethods(). It's good
practice to write tests so that they work no matter what order
they are run in - in fact it's vital because you don't know what
that order will be.

It turns out that this surprising order is recorded if you
look very hard. For example href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4789197">bug
4789197 includes the text "In the HotSpot VM, the order is
actually dependent on the order in which the VM's UTF-8
constants are created, i.e., order will be different if a
program loads other classes which uses the same name-type id's
in random order."

Anyway, now I'm faced with a dilemma. I'd quite like to
restore the previous behaviour of Standard MBeans, where
attributes and operations show up in the same order as the
methods in the MBean interface. Code that depends on this is
surely broken, but code like JConsole that presents the
attributes and operations in a user interface would surely be
better served by this arrangement. Except that now I know that
that order can be perturbed by unrelated classes that happen to
be loaded earlier. Is it still worth trying to preserve this
fragile order? I'd be interested in what people think.

Related Topics >>