ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Inside Class Loaders: Debugging
Pages: 1, 2

Patching the JDK's Class Loader for More Debug Information

Due to the simple implementation of the JDK's class loaders, retrieving useful problem-solving data is painful and time consuming. One way to speed things up is to patch the JDK class loaders by adding a toString() method that prints out the resources it can load and the class loader it derives from, and then calls the toString() method of the parent, if set. With this, a single ClassLoader.toString() call provides you with all of the data you need. These are the steps to patch the JDK's class loaders:



  1. Take the source of java.lang.ClassLoader, java.security.SecureClassLoader and java.net.URLClassLoader and copy them to a safe place.
  2. Add the desired toString() method and make sure that every class loader prints out its own address by using super.toString() so that you can test if class loaders from the same class are identical or not.
  3. Jar them up
  4. Add -Xbootclasspath/p:<the archive you just have created> to your script that starts Java.

Because we used /p: for the bootclasspath, the given archive(s) are loaded by the bootstrap class loader before any other archive, including rt.jar.

Note: Please do not patch classes of your own this way, because if any of these patched classes contain a symbolic link to an unpatched class, you will get a linkage error. This is because your classes are made available to the System class loader or other child class loaders of the bootstrap class loader (unless you added your archives to the JDK's lib directory, which is not very smart).

Now let us have a look at the patch code to see how you get your information about the class loading process. Note that java.lang.ClassLoader and java.security.SecureClassLoader do not need to be patched in this case, but when you want to add trace-logging statements into the loading process you can do so.

Patching java.net.URLClassLoader

We need three pieces of information from the class loader. First, we need an indicator of the instance, because we need to distinguish two instances of the same class. Then we need to know which archives are available to this class loader, so that we can check later if an archive/class is missing. Finally, we need to print out the information from the parent class loader, if it is available. For example, the System class loader must call toString() on the bootstrap class loader. This would look like the following:

public String toString() {
  if( getParent() != null ) {
     return "java.net.URLClassLoader:\n"
        + "hashcode: " + hashCode() + "\n"
        + "URLs: " + java.util.Arrays.asList(
           getURLs() ) + "\n"
        + "parent { " + getParent() + " }\n";
  } else {
     return "java.net.URLClassLoader:\n"
        + "hashcode: " + hashCode() + "\n"
        + "URLs: " + java.util.Arrays.asList(
           getURLs() ) + "\n";
  }
}

Our code first checks if there is a parent. If so, we print the name of the class, its hashcode (which is a pretty good indicator for checking two instances), and then the list of archives available to the class loader. Finally, we delegate the call to the parent, if a parent is available. A printout can look like this:

Note: Indentation and wrapping were added by hand to illustrate the point!

java.net.URLClassLoader:
hashcode: 33513127
URLs: [
 file:/C:/private/madplanet/investigations/
 classloader.part2.advanced/target/test-classes/,
 file:/C:/private/madplanet/investigations/
 classloader.part2.advanced/target/classes/,
 file:/C:/Documents%20and%20Settings/aschaefer/
 .maven/repository/log4j/jars/log4j-1.2.8.jar,
 file:/C:/Documents%20and%20Settings/aschaefer/
 .maven/repository/junit/jars/junit-3.8.1.jar,
 file:/C:/java/maven-1.0-rc1/lib/ant-1.5.3-1.jar,
 file:/C:/java/maven-1.0-rc1/lib/
 ant-optional-1.5.3-1.jar
]
parent { java.net.URLClassLoader:
   hashcode: 10430987
   URLs: [
    file:/C:/java/jdk.1.4.2/jre/lib/ext/
       dnsns.jar,
    file:/C:/java/jdk.1.4.2/jre/lib/ext/
       ldapsec.jar,
    file:/C:/java/jdk.1.4.2/jre/lib/ext/
       localedata.jar,
    file:/C:/java/jdk.1.4.2/jre/lib/ext/
       sunjce_provider.jar
    ]
}

It looks bad here, but in a log file it is quite readable. The listings show all the archives and directories of the System class loader (the upper part) and the Bootstrap class loader (the lower part). This should help to find the missing archives or directory.

By the way, if you like or need indentation, you can either add the method toString( int indentationLevel ) to the class loaders, but then you need to change your code when you patch the class loaders. You could also use a Thread Local to carry the indentation, which requires to create the thread local instance before you invoke toString on the class loader. Even though I deal with class loaders quite often, I prefer a solution where I do not have to change my code at all, even it requires me to look harder or do the indentation by hand. Of course, class-loader problems should not be our daily business, anyway.

Patching Your Own Class Loaders

I do prefer to change my own class loaders so that they contain a name, which can be used to determine which class loader is which. This allows me to provide a human-readable indicator for the class loader instance instead of relying on the hashcode. For example, in a J2EE server you could add the enterprise application name to the class loader so that when you print out the class loader, you know to which application it belongs. This, of course, requires you to change the class loader as well as the classes where the class loader are created, but I think it is worthwhile. Implementing a class loader name looks like this:

public class  MyClassLoader
  extends ... {

 private String mName;

 public MyClassLoader(String pName, ... ) {
   super();
   mName = pName;
 }
...
 public String toString() {
  return "MyClassLoader:\n"
   + "name: " + mName + "\n"
   + <print out the what this class loader
     can load>   
   + "parent { " + getParent() + " }\n";
 }
...

If you cannot change the code, you can still patch your class loaders the same way we did with the java.net.URLClassLoader, by overriding the toString() method. But keep in mind that you must add the patched class loader to the same class loader to which the non-patched classes is added. So if you add your class loader to the class path, then you must add the patch archive to the class patch as well, but just in front of the archive with the regular class loader. Adding the patched classes to the same class loader as the non-patched classes will prevent class-loading problems no matter what the delegation model.

Conclusion

Dealing with a class-loader problem is a time-consuming process, no matter what you do; preventing a class-loading problem in the first place is much faster than dealing with it. Therefore, I encourage you to write as many tests as you can and try to execute them in an environment that is as close as possible to the actual runtime environment. Linkage errors are caused by a runtime class loader environment that is different than the compilation environment. Normally, during compilation you have a flat class-loader hierarchy, adding all of the archives you need to a single class loader. At runtime, you often have a hierarchical class-loader structure for various reasons, which requires a great deal of testing. Nevertheless, it is pretty easy to run into a class-loading problem after all, especially when your code is hosted in some sort of a container, such as a J2EE server, a web server, etc. In this case, patched class loaders enable you to retrieve the data necessary to pin down a problem faster and easier than with either "trial and error" or by running a debugger, which is not always possible.

I wish you good luck in hunting your very own class-loading problems. Hopefully, the techniques discussed here will help.

Andreas Schaefer is a system architect for J2EE at SeeBeyond Inc., where he leads application server development.


Return to ONJava.com.