The Rutgers Dynamic Class Loader Overview This is an implementation of a dynamic class loader that can reload class files whenever they change on disk. The implementation understands class hierarchies and forces dynamic reloading of classes whenever classes referred to by them change. The current version does not support jar or zip files. The loader can be used by creating an instance of ClassCache. The ClassCache constructor accepts one parameter which is an instance of a LoaderControl object which controls of the behavior of the ClassCache. LoaderControl is a abstract class whose signature is as follows: public abstract class LoaderControl { public String[] classPath=null; public String[] excludedClasses=null; public String[] excludedHashes=null; public abstract String getHashString(String name); public abstract boolean toBeLoaded(String name); } getHashString is a function that given a class name returns a hash value. This hash value is used by the ClassCache to group classes together. Each hash value is assigned its own bucket, and each bucket is assigned its own ClassLoader. toBeLoaded is a function that takes a class names and tests whether or not this ClassCache should load the class. classPath is a an array of strings denoting directories in the classPath that should be used by the ClassCache instance to search for files. excludedClasses is an array of classnames that this ClassCache should refuse to load even if it is accepted by the toBeLoaded function. excludedHashes is an array of hashes that should similarly be rejected by the ClassCache instance. The ClassCache extends ClassLoader and should be used as an ordinary ClassLoader. A sample usage is as follows: import loader.*; ClassCache classCache = new ClassCache( new LoaderControl() { {this.classPath=new String[] {"."}; this.excludedClasses=new String[] {"X"}; this.excludedHashes=new String[0]; } public String toBeLoaded(String name) { return names.startsWith("X"); } public String getHashString(String name) { return name; } } ) Here we have used an anonymous inner class to create an instance of a LoaderControl object that loads all classes in the current directory whose class names start with the string X except the class (or interface) X, sets the classPath to the current directory and uses the identity function as the hash function. To load a class say X1 which extends (or implements) X, you can use this as follows: X x=(X) classCache.loadClass("X1").newInstance(); or X x=(X) Class.forName("X1",true,classCache); Each time loadClass is called, ClassCache tests whether the required class files are current and reloads them if necessary. Note that instances of loaded classes (in this case X1) that have already been loaded are unaffected when such a reload is performed. Also note that by specifying that X should not be loaded by the classCache, we can actually use the cast in our program. The package also includes two abstract extensions of LoaderControl, PackageLoaderControl which uses the name of the package as the hash value and ClassLoaderControl which assigns all classes the same hash value "class". ____________________________________________________________________________ Technical Details The ClassCache uses an instance of DisposableClassLoader to actually load classes. One instance of the class is used for each unique hash string that gets loaded. Thus if you are using the identity function as hash value generator, one instance of DisposableClassLoader will be associated with each class that is loaded by the ClassCache. Each time a class is loaded using an instance of the ClassCache, the ClassCache first checks if it is a class to be loaded by it using the canLoad() interface function in the LoaderControl class. This function uses the toBeLoaded function provided by the class and the values of excludedHashes and excludedClasses to determine if this class is within this ClassCache instances "namespace". If not, loading is delegated to the System. If the class is within the ClassCache instances namespace, the ClassCache object now computes the hash value of the class name and checks if the a DisposableClassLoader is associated with this hash value. If yes, the system checks if either this class loader or any class loader for a class referred have a class that was modified on disk since it was loaded. If so, ClassLoaders for the entire subtree that refers directly on indirectly to this ClassLoader is "dumped" and a new DisposableClassLoader object is allocated for this class. Otherwise if no DisposableClassLoader is associated with this hash, a new one is allocated. Once a DisposableClassLoader is found for a given hash, the ClassCache object now delegates the work of loading the class to the DisposableClassLoader. The DisposableClassLoader, performs the reverse check using canLoad() and also makes sure that the class has the hash associated with this DisposableClassLoader. If not the loading is delegated back to the class cache. Checks are in place to make sure that mutual recursion never takes place. The actual algorithm to test whether a ClassLoader should be thrown out or not is based on references and building a class hierarchy in the ClassLoaders. For example, consider the following class hierarchy and the sample classcache in the first section. X / \ XX1 XX2 / \ | \ XXX1 XXX2 XXX3 XXX4 | XXXX Let us assume that the entire hierarchy has been loaded. Note that X will not be loaded by the classCache because of the exclusion list. Suppose we now try to load XXX2. The classCache checks all classes (note that the hash function is identity, otherwise this discussion applies to hashed values), from the path between XXX2 and root (X), i.e. XX1 and XXX2. If any of these classes say XX1 is changed on disk, then the entire subtree below XX1 is thrown out and must be reloaded. Thus all classes XX1, XXX1, XXX2 and XXXX must be reloaded. Thus the cost of reloading and testing is directly proportional to the depth of this tree. Hence a good hash function is one that reduces the depth of this tree. On the other hand if all classes are assigned the same hash, then each time a class file changes on disk, all classes will be reloaded. (e.g. this is the approach taken by AdaptiveClassLoader from apache)