June 12, 2009

thoughts on Nokia class loading mechanism

situation:
Imagine you have your basic midlet class (say gui.Midlet). This class references another class (gps.InternalProvider) which implements a specific interface (gps.LocationService). Note that there is no reference to the actual class gps.InternalProvider, except for instantiation, and that is pretty well hidden in a function under several ifs and switches. Everything else is done through the interface.
Class gps.InternalProvider references classes from an api (JSR-179) that might or might not be present on your target device (S40 Nokia). It uses them rather extensively, but of course, it can't use them until it's instantiated. (no static codeblocks, nothing like that)

issue:
When such midlet is started on such device, it instantly dies with NoClassDefFoundError on the JSR-179 classes. It doesn't even start, I'm pretty convinced that none of my code is executed.
Definitely not the part that would instantiate the offending classes.

solution:
Create a distraction. Add a class (let's call it gps.InternalProviderRedirector) that has only one static method:
public static LocationService instantiate() {
return new InternalProvider();
}
Then instead of doing this directly in the Midlet class, call InternalProviderRedirector.instantiate(). Magically, it will work.

thoughts:
This is easy to grasp intuitively (for me, at least), but in some situations, that is not enough. So i studied the JVM specification, especially the parts about class loading and linking, and tried to come up with a scientific explanation to this phenomenon. Here's my best effort - note that it is only an educated guess and might not relate to reality in any way.

The spec says that when you are loading a class, you get symbolic references to all classes in use. Then, in the linking step, you can (but don't have to) resolve those symbolic references by trying to load the referenced classes.
I say that Nokia does this. That means that when linking the Midlet class, the class file for InternalProvider (or InternalProviderRedirector) is already loaded.
Then, either in the initialization phase or when the first code from a class is run, Nokia JVM attempts to link all the referenced classes. That means that those classes now try to load and resolve their symbolic references.
When you start the midlet, the class Midlet is loaded. Then it's linked, triggering loading of InternalProvider. And then it's instantiated, triggering linking of InternalProvider. That triggers loading of JSR-179 classes, which are not present, so the instantiation itself fails.
When you insert InternalProviderRedirector into the chain, InternalProvider is never linked (only loaded), so JSR-179 is never loaded. And all is well.

No comments: