DEBUGGING CLASS LOADING
This tip was developed using Java(tm) 2 SDK, Standard Edition,
v 1.3.
The previous Class Loaders mechanism included a quick
overview of the ClassLoader architecture. Unfortunately, even
after you have a good understanding of the architecture it is
easy to get lost when debugging a complex system with multiple
class loaders. This tip will help you troubleshoot common
class loading problems. Begin by compiling these classes:
public class LoadMe {
static {
System.out.println("Yahoo! I got loaded");
}
}
import java.net.*;
public class Loader {
public static void main(String [] args) throws Exception
{
URLClassLoader uclMars = new URLClassLoader(new URL[]{new URL("file:mars/")});
URLClassLoader uclVenus = new URLClassLoader(new URL[]{new URL("file:venus/")});
Class mars = Class.forName("LoadMe", true, uclMars);
Class venus = Class.forName("LoadMe", true, uclVenus);
System.out.println("(Venus version == mars version) == " + (mars == venus));
}
}
Before running the Loader class, create three copies of the
compiled LoadMe.class file: one in the same directory as Loader,
one in a "mars" subdirectory, and one in a "venus" subdirectory.
The objective of this test is to load two different versions
of the same class. (Before reading further, see if you can
determine why this isn't going to work.) When you run the Loader
class, you will see the following output:
Yahoo! I got loaded
(Venus version == mars version) == true
Contrary to the plan, the mars and venus versions of the class
are the same. A first step to debugging this is to use the
-verbose:class flag on the command line:
java -verbose:class Loader
[Opened E:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Opened E:\Program Files\JavaSoft\JRE\1.3\lib\i18n.jar]
[Opened E:\Program Files\JavaSoft\JRE\1.3\lib\sunrsasign.jar]
[Loaded java.lang.Object from E:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
...
[Loaded Loader]
[Loaded LoadMe]
You should see several screens of output listing all the classes
as the VM* loads them. For classes loaded by the bootstrap class
loader, this output will show you exactly what JAR file the class
came from. This information alone should quickly resolve many
class loader problems. For example, it would help you identify
the fact that you are accidentally running with your JAVA_HOME
environment variable pointing to another installed copy of the
Java(tm) platform. Unfortunately, the output does not contain
enough information to solve the LoadMe problem. Although the
output clearly shows that only one copy of the LoadMe class was
loaded, it does not show where the class came from.
To get even more information, you can install a version of
URLClassLoader that logs every class. In order to do this, you
need to recompile java.net.URLClassLoader, and then order the VM
to use your "hacked" version. (Using a hacked version of a core
API class should be used for debugging purposes only and to
explore the VM.)
Here is a replacement of URLClassLoader with logging added:
//extract java.net.URLClassLoader from src.jar in your JDK directory
//to a "boot" subdirectory. Insert the following method and recompile
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
Class cls = null;
try {
cls = super.loadClass(name, resolve);
return cls;
}
finally {
System.out.print("Class " + name);
if (cls == null) {
System.out.println(" could not be loaded by " + this);
} else {
ClassLoader cl = cls.getClassLoader();
if (cl == this) {
System.out.println(" loaded by " + cl);
} else {
System.out.println(" requested by " + this + ", loaded by " + cl);
}
}
}
}
Notice the comment in the URLClassLoader replacement. First
extract java.net.URLClassLoader from src.jar in your JDK directory
to a "boot" subdirectory. Insert into it the loadClass method.
Then recompile URLClassLoader.
Notice that the logging method is explicit about class loader
delegation. If one class loader is asked for a class, but its
parent class loader returns the class first, the output reports
both class loaders.
Now, you can use the "prepend" version of the bootclasspath
flag to force this version of URLClassLoader to be loaded instead
of the normal one:
java -Xbootclasspath/p:boot/ Loader
If you search the console output for the string "LoadMe" you
should find something like this:
Class LoadMe loaded by sun.misc.Launcher$AppClassLoader@404536
Class LoadMe requested by java.net.URLClassLoader@5d87b2, \
loaded by sun.misc.Launcher$AppClassLoader@404536
This output immediately identifies the problem. The LoadMe class
is not loaded by the URLClassLoader because it is already visible
on the CLASSPATH; it is represented here by a member class of
sun.misc.Launcher. To fix the bug, remove the copy of the LoadMe
class from the main project directory.
This is only one example of how you can use a custom version of
a core API class to aid debugging. You can use the boot class path
anywhere you need to inject debugging code into the core API.
But you need to understand exactly what you are doing -- a
defective version of a core API class can compromise the entire
VM. Also, the the license forbids shipping a modified core class.
As mentioned earlier, you should use this technique only to
debug applications and explore the VM, never to ship code to
a customer.
For more on using the bootclasspath, see the white paper
"Using the BootClasspath" by Ted Neward at
http://www.javageeks.com/Papers/BootClasspath/
|