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

advertisement

AddThis Social Bookmark Button

Inside Class Loaders: Debugging Inside Class Loaders: Debugging

by Andreas Schaefer
06/30/2004

Though we discussed the basics of class loading in the previous article in this series, we still need more knowledge before we can delve into the advanced class-loading techniques. This article will show how to solve class-loading problems and to overcome some debugging limitations of the JDK class loaders.

Let me issue a warning here that dealing with class loaders is somewhat risky business, because problems seldom arise during the class-loading process itself. Therefore locating a problem is tricky and time-consuming. That said, custom class loaders can also offer many possibilities regular Java classes cannot achieve, and therefore are essential to advanced applications like servers, code-enhancing tools and dynamic modules. In the end, it is up to the architect to decide if the features outweigh the risks and to make sure that debugging and extended tests are provided.

Tricks of the Trade to Investigate Class-Loading Problems

The biggest challenge in dealing with class-loading problems is that problems rarely manifest themselves during the class-loading process but rather during the usage of a class later on. In addition, the messages provided by the JDK are, to say the least, inadequate, and the toString() methods of the JDK class loaders are not overridden to provide more data.

ClassNotFoundException

Let's consider several different class-loading error scenarios. First, we want to figure out what to do when a class could not be found and the application throws a ClassNotFoundException that looks like this:

java.lang.ClassNotFoundException: Class: com/madplanet/article/classloader/
   part2/NotToBeFoundClass could not be found
	at com.madplanet.article.classloader.
         part2.MyClassLoader.findClass
         (MyClassLoader.java:41)
	at com.madplanet.article.
         classloader.part2.MyClassLoader.
         loadClass(MyClassLoader.java:56)
	at java.lang.ClassLoader.
         loadClass(ClassLoader.java:235)
	at com.madplanet.test.classloader.
         part2.MainTest.testClassNotFound(
         MainTest.java:48)

Here are some steps to resolve the issue:

  1. Look for the line where your class loader's loadClass() call is involved (in our example, the last line).
  2. Figure out what class loader is used there.
  3. Figure out the parent class loaders recursively until you find the bootstrap class loader.
  4. Figure out why the class cannot be found by any of these class loaders.

This problem is caused when a class loader is asked to load a particular class and it cannot be found. Here are few causes:

  • An archive, directory, or other source for the classes was not added to the class loader asked to load the class, or to its parent.
  • A class loader's parent is not set correctly.
  • The wrong class loader is used to load the class in question.

Point two seems to be a little bit strange, but when a class loader is created, its parent is set once and for all, and does affects class loadings. For the simplicity of the example, let us assume that an application class loader would have the bootstrap class loader set as its parent. This means that this class loader cannot load any class added to the class path because these are made available through the System class loader. This scenario will rarely occur, except deliberately, because the bootstrap class loader is not made available like this. A case for doing this would be when an application wants to prevent users from adding classes via the class path.

Point three can happen more easily than you might think. For example, in J2EE, an EJB is required to use the thread context class loader (Thread.currentThread.getContextClassLoader()) to load any class from its application. It is up to the discretion of the application server to specify the class loader of the EJB itself. I've seen many applications fail because developers used Class.forName without specifying the thread context class loader as the current class loader.

Later I will show a way to figure out all of the necessary information needed here with a simple class loader toString() method call, even for JDK class loaders. If you still cannot figure out why it is failing, consider the possibility that a class loader is not following the JDK delegation model or is delegating to class loaders other than the parent.

Symbolic-Link Class Not Found

Another problem arises if a symbolic reference cannot be found, in which case a NoClassDefFoundError will be thrown, indicating that there is a linkage problem. That means the code has been successfully compiled, but at runtime, the class can no longer be found. This error is sometimes thrown between the loading of the class containing the reference and the use of the reference. The stack trace looks like this:

java.lang.NoClassDefFoundError: 
   com/madplanet/article/classloader
   /part2/ClassNotAvailableDuringRuntime
	at com.madplanet.article.classloader
         .part2.ClassWithFailingReference.test
         (ClassWithFailingReference.java:24)
	at sun.reflect.NativeMethodAccessorImpl
         .invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl
         .invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.
         DelegatingMethodAccessorImpl.invoke
         (DelegatingMethodAccessorImpl.java:25)

As you can see, the outer class ClassWithFailingReference was created without a problem and then fails when test() method is invoked. This demonstrates that a class can be successfully loaded and used and still fail later when a particular reference is used. It is up to the JDK to decide when it attempts to load the class (and thus throw the error) due to the lazy class loading allowed in the Java language specification.

Note: In contrast to the previous problem, here the class loading is done implicitly by the JDK instead of invoking a class loader's loadClass() method. In addition, an Error is thrown in this case instead of an exception, and some applications tend to swallow errors.

Here are some steps to investigate and solve the problem:

  1. Find the NoClassDefFoundError and print out its stack trace.
  2. Find the topmost line in the stack trace that is not class-loader-related. This most likely indicates the class containing the symbolic link that was not found.
  3. Figure out the class loader of the class containing the offending symbolic link.
  4. List the parent class loaders recursively.
  5. Figure out why the class is not available to any of these class loaders.

This error is thrown when an implicit class loading fails. Here are a few potential causes:

  • An archive, directory, or other source for the classes was not added to the class loader asked to load the class, or to its parent.
  • A class loader's parent is not set correctly.
  • Symbolic links in a class are unaccessible by the containing class's class loader, such as a child class loader.

The first two points are the same as for the "class not found" problem and should be treated the same way, with the only difference being the the implicit class loading.

The most likely reason is that a class is loaded by a class loader with references that are in an inaccessible class loader, such as a child class loader. Assuming Log4J is loaded by a J2EE server class loader that is a child of the System class loader (CL stands for class loader):

  -- Bootstrap CL
   +-- System CL
     +-- J2EE server CL -> log4j.jar

To debug a problem, an archive is added to the class path containing a patched Log4J Category class:

  -- Bootstrap CL
   +-- System CL -> org.apache.log4j.Category
     +-- J2EE server CL -> log4j.jar

When the J2EE server is started, Log4J classes are loaded by the server's class loader except for the patched Category, which is loaded by the System class loader. But this Category contains symbolic links to other Log4J classes (such as the class Priority), which are unavailable to the System class loader because they are only available to the server's class loader. To solve this problem, it is not only necessary to check if a class is made available to a class loader, but also to a class loader that is accessible by the class loader of the containing class. To make matters more complicated, the accessibility depends on the delegation model, and for the JDK's model, this means that any symbolic link class must be made available to the same class loader or a parent class loader of the containing class.

In a situation where there is no way to correct the problem, the only other solution is not to use a symbolic link but instead load this class manually and use reflection to use the instance. This is especially important when the class is not available on the class' class loader but the Thread context class loader, as in J2EE.

Note: A custom class loader has the freedom to delegate requests to any class loader, such as a child class loader or a class loader on another branch (any class loader is at least a child of the bootstrap class loader except the bootstrap class loader itself) at any time in its loadClass() method. The only exception are classes in the java. package that can only be loaded by the bootstrap class loader. So be warned and expect the unexpected when a custom class loader overrides loadClass().

Native Library Problems

One of the nastier problems actually has nothing to do with the class loader, but for the sake of completeness I will discuss it here. Whenever a native library is loaded (via System.loadLibrary() or Runtime.loadLibrary()), the JVM retrieves the class loader of the calling class. Whenever the archive is already loaded by a class with another class loader, the loading will fail with an UnsatisfiedLinkError like this:

java.lang.UnsatisfiedLinkError:
   Native Library C:\temp\dll.loading.tests\
         myruntime.dll already loaded
         in another classloader
	at java.lang.ClassLoader.loadLibrary0(
         ClassLoader.java:1525)
	at java.lang.ClassLoader.loadLibrary(
         ClassLoader.java:1456)
	at java.lang.Runtime.load0(Runtime.java:737)
	at java.lang.System.load(System.java:811)
	at foo.dll.TestClass.load(TestClass.java:28)

The only way to resolve this issue is to make sure that you call loadLibrary() or load() from a class that is loaded by a general class loader (like the System class loader) as well as the classes containing the native method declarations (JNI bridge classes). If the JNI bridge classes are loaded by a different class loader the calls to it will fail, too. To solve these issues, I would suggest the following steps for an environment with multiple class loaders:

  • Create a central repository to load the native libraries as well as the JNI bridge classes.
  • Any JNI bridge classes should only contain the native method declarations.
  • Provide an archive that only contains the JNI bridge classes for a particular native library.
  • Remember that his error has nothing to do with a class not being found, but with a mismatch of class loaders accessing native code.

Also remember that you will get UnsatisfiedLinkError when your bridge classes are not set up correctly, including situations where you have the wrong package name, class name, method name or method signature; all of which can be avoided by running simple tests.

Pages: 1, 2

Next Pagearrow