MXJ Class Loading
This is a quick rundown of how the MXJ class loader works, and the implications for class variables (statics).
The first time an MXJ object is instantiated in Max/MSP, a JVM is launched to host the MXJ objects. All MXJ objects are loaded into this JVM by a class loader.
The class loaders is responsible for reading class files from disk and creating Class objects in the JVM, so that actual Java object instances can be created from the classes. Classes are cached: the first time a class is sought, it comes from disk, and on subsequent searches the previously loaded copy is used. Only one Class object will be loaded and used for each class, regardless of how many instances of that class are created. Hence, any class variable (static) will be unique.
MXJ implements automatic reloading of class files: if a class file is modified, then the class will be reloaded. This reloading check is done only when a new MXJ object is created in Max/MSP: it is not a background activity (as in, say, Tomcat).
The reloading is done by discarding the current class loader and building a new one. The existing cache of Class objects is discarded - but the already loaded Class objects, and object instances, are left in place.
The upshot of this behaviour is: when a class file is changed on disk, subsequent MXJ objects will be created with a new class loader (which will cache Class objects), and this class loader will be used for all new MXJ objects up until a new MXJ object has a class file on disk which is newer than the cached Class. At this point a ~~new~~ class loader will be created. And so on.
As a result, class variables behave slightly oddly. Class variables are shared only if they were loaded by the same class loader, and the creation of a new class loader will not impact existing MXJ objects.
Here is an MXJ example to illustrate this behaviour:
package net.loadbang.mxj.demo;
import com.cycling74.max.*;
/** A little test object to show the behaviour of the MXJ
classloader. */
public class StaticTest extends MaxObject {
private static int theCounter = 0;
public StaticTest() {
theCounter++;
createInfoOutlet(false);
declareTypedIO("A","I"); // bang in, int out
}
public void bang() {
outlet(0, theCounter);
}
}
Create several MXJ instances over this class, and then bang them: they will all put out the same integer value (which should be the number of MXJ objects created). Each time a new MXJ object is created, every existing one will output the new total.
Now update the class file (by forcing a recompilation) and create some new MXJ objects. These new objects will collectively start counting again from zero. If an existing MJX object is edited, it will join the new group. The old MXJ objects will still share the old total, but no new objects can be added to this group. In effect, there are now two distinct classes called StaticTest, both with instances in Max/MSP, both running, but unable to share data or communicate in the Java heap.
It goes without saying that a new class loader loses the link to ~~all~~ existing classes. Here's a modified version of StaticTest which uses a second class to hold the state:
package net.loadbang.mxj.demo;
import com.cycling74.max.*;
/** A little test object to show the behaviour of the MXJ
classloader. */
public class StaticTest extends MaxObject {
private int itsCount;
public StaticTest() {
Counter.bump();
createInfoOutlet(false);
declareTypedIO("A","I"); // bang in, int out
}
public void bang() {
outlet(0, Counter.value());
}
}
When the class file for StaticTest is modified, new MXJ objects start counting afresh. There was no change to the Counter class maintaining the state, but the new class loader brings in a new Class instance for Counter.
(As an aside, updating the class file for Counter has no effect; MXJ is presumably just examining class files for the objects actually instantiated directly by MXJ.)
by rothwell on January 30, 2006