Hi all,
I've long had an impression that all class loaders in an loadClass() chain
should be recorded as initiating loaders. That knowledge comes from Inside the
Java Virtual Machine, 2nd Edition [1]:
But it looks like that's an incorrect impression.
According to the JVMS2e 5.3.2 [2], the "Otherwise" paragraph, only when the
JVM initiates a class loading process, the loader directly invoked by the JVM
will be recorded as the initiating loader; the ones in the middle of the
delegation chain won't be recorded. A loader whose loadClass() is called
explicitly (and successfully completed) from Java code isn't necessarily
recorded as an initiating loader.
Using the example in [1] in the "Now imagine" paragraph, only Cindy should be
an initiating loader of java.io.FileReader; neither Mom nor Grandma should be
recorded as an initiating loader.
I've tried writing an actual code to emulate the example, and on JDK6, it
shows the description in [1] is wrong.
-----------------------------------------------
TestInitiatingLoader.java
import java.io.*;
import java.net.*;
public class TestInitiatingLoader {
public static void main(String[] args) throws Exception {
Grandma grandma = new Grandma();
Mom mom = new Mom(grandma);
Cindy cindy = new Cindy(mom);
cindy.loadClass("Dummy").newInstance(); // force class init
final String reader = "java.io.FileReader";
printStats(grandma, reader); // false
printStats(mom, reader); // false
printStats(cindy, reader); // true
}
private static void printStats(LoadedClassQueryable loader, String name) {
System.out.printf("Is %s an initiating loader of %s: %b\n",
loader.getClass().getName(),
name,
loader.foundLoadedClass(name));
}
}
interface LoadedClassQueryable {
boolean foundLoadedClass(String name);
}
class Cindy
extends URLClassLoader
implements LoadedClassQueryable {
public Cindy(ClassLoader parent) {
super(makeArgs(), parent);
}
private static URL[] makeArgs() {
try {
return new URL[] { new File(".").toURI().toURL() };
} catch (Exception e) {
e.printStackTrace();
return new URL[] { };
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if ("Dummy".equals(name)) {
// force Cindy to load this Dummy class
return findClass("Dummy");
}
return super.loadClass(name);
}
@Override
public boolean foundLoadedClass(String name) {
return findLoadedClass(name) != null;
}
}
class Mom
extends ClassLoader
implements LoadedClassQueryable {
public Mom(ClassLoader parent) {
super(parent);
}
@Override
public boolean foundLoadedClass(String name) {
return findLoadedClass(name) != null;
}
}
// use system class loader as parent
class Grandma
extends ClassLoader
implements LoadedClassQueryable {
@Override
public boolean foundLoadedClass(String name) {
return findLoadedClass(name) != null;
}
}
-----------------------------------------------
Dummy.java
import java.io.*;
public class Dummy {
static {
// Dummy should be loaded by Cindy.
// So Cindy will be recorded as an initiating loader for java.io.FileReader
try {
new FileReader("Dummy.class").close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
-----------------------------------------------
Could anybody clarify which loaders are supposed to be recorded as an
initiating loader?
[1]: http://www.artima.com/insidejvm/ed2/linkmod3.html
[2]:
http://java.sun.com/docs/books/jvms/second_edition/html/ConstantPool.doc.html#79441