Thursday, September 06, 2007

Is final not final, or just not the final I wanted?

While adding some VM crash protection to JNA, I ran into this situation:


public class Pointer {
public static final int SIZE = Native.POINTER_SIZE;
}

public class Native {
public static final int POINTER_SIZE;
static {
initIDs();
POINTER_SIZE = pointerSize();
}
private static native int pointerSize();
}


I had just moved the native initialization code from Pointer to Native. All the native methods were private, so the only changes were to update those two classes and rename the native methods. But when I reran the test suite, I started getting a host of errors.


There errors were all caused by Pointer.SIZE having a zero value. I figured that since Pointer.SIZE was final, the value it was assigned from must also have been zero. A quick inspection on Native.POINTER_SIZE, however, showed that it was propertly initialized and non-zero. How, then, could Pointer.SIZE be zero if Native.POINTER_SIZE was not?


Anyone who has done any JNI is probably aware that Java access modifiers don't mean anything to native code. My first thought was that perhaps "final" modifiers don't mean anything either. Only problem is that I was never touching that Pointer.SIZE field in native code. Never even accessed it.


Scanning though the native initialization code, I noticed that, among a host of standard JRE classes, a reference to the Pointer class was being created. This turns out to be the cause of my problem: during Native class initialization, the Pointer class was loaded from JNI. When the pointer final field was initialized, it got the value from a not-yet-initialized Native class.


Turns out, this can be duplicated with pure Java code:


public class LoadTest {
public static final int VALUE;
static {
System.out.println("value=" + Secondary.VALUE);
VALUE = 1;
System.out.println("value=" + Secondary.VALUE);
}
}

class Secondary {
public static final int VALUE = LoadTest.VALUE;
}


When loaded, the class prints "value=0" twice, so final is final, just not the value I intended. Here, the problem is more apparent because the class dependency is clearly visible, which wasn't the case with the JNI initialization.


Maybe I'll call this antipattern something curiously obscure, like "Law of the Polygamist's Second Wife".

2 comments:

Anonymous said...

I think this is because when Pointer.SIZE is initializing, Native.POINTER_SIZE is not (i.e = 0)
Then Native.POINTER_SIZE is initialized, but Pointer.SIZE is not modified.

So values are final but you have an initialization oreder problem. Try to declare class Pointer after class Native to see if it works like you want.

Rob Di Marco said...

One possibility is that you have a race condition.

in your static method you are calling the initIds. If that were a long running method (relatively), could the native code in a different thread be making a call to the variable before the static initializer finishes?

In the JVM, threads should block waiting for the static initializer to finish, but could the native code not be blocking?