Classloaders are responsible for finding and loading classes that are requested in Java code executed inside a Java Virtual Machine. Classloader-related issues are among the most difficult to troubleshoot in the Java/J2EE technology stack.
The Java Language and JVM Specifications define a Delegation-based Classloader architecture. This means every classloader has a parent classloader (which must be consulted before attempting to load a class) except for the Primordial Classloader.
Troubleshooting classloader problems can be difficult. Trying to troubleshoot classloader-related issues without understanding the Java Classloader Architecture will be even more difficult. This article aims to bring together the numerous resources available on the web to answer basic questions for which the author has found himself searching.
The Java Virtual Machine Specification and Java Language Specification define requirements for classloaders.
Java Virtual Machine Specification
The JVM Specification can be found here.
The Specification defines the concept of loading a class.
The Specification defines the requirements of the bootstrap classloader.
The Specification also defines the requirements of a user-defined (custom) classloader.
The JVM Specification makes reference to the System classloader, but it does not define the use of the System Classloader or Extension Classloader.
The Java Class file format is also defined by the Specification.
Java Language Specification
The Language Specification further defines details of loading classes and interfaces in section 12.2.
The Extensions Mechanism defines the Delegation Classloader model. The extensions Mechanism was introduced in Java 1.2. The java.lang.Classloader API Specification defines the classloader super class–this was introduced as part of the Extensions Mechanism. An explanation of the design required is given here.
Classloaders have a parent-child relationship; all classloaders except the Primordial Classloader have a parent. Before a particular classloader attempts to load a class, it will give the request to its parent classloader. Each child classloader hands the request to its parent until the request reaches the Primordial classloader (see below). If this top-level classloader cannot resolve the class name, then the request is given back to the child classloader. Ideally, as the request makes its way back down the hierarchy, one of the classloaders will be able to resolve the name. If not, an Exception will be thrown. The Specifications define what errors are thrown under what circumstances.
Once a class has been located, all classes and properties files referenced by that class must also be resolvable by the same classloader or a classloader higher up the hierarchy. Otherwise, an error is thrown. The links provided in the Reference section describe the errors that can be thrown by classloaders.
Every JVM is required to provide three classloaders per the JVM & Java Language Specifications (described above):
- Primordial classloader
- Extensions classloader
- System (or Application) Classloader
This classloader is responsible for loading the Java libraries that come with JRE. Classes located in rt.jar and other JAR files in $JRE_HOME/lib will be loaded by this classloader.
This classloader will be implemented as native code–there has to be a starting point.
It loads the java.lang.Classloader class that serves as the classloader or super class of other classloaders in the hierarchy. Only the Primordial classloader can load classes in java.* libraries. This is a Java security feature.
The Primordial bootstrap classpath can be modified to include other jar files to its classpath by prepending or appending to the default list. See the JVM documentation for how to accomplish this. Note, however, this is generally not the recommended approach to solving problems, but sometimes it is the only way.
The Extensions Classloader classpath contains jar files listed in $JRE_HOME/lib/ext (and possibly other locations depending on the vendor and platform) or any other library listed in java.ext.dirs system property. The Extensions Classloader was introduced with the Extensions Mechanism mentioned in the Java Language Specification in Java 1.2.
This classloader is implemented by the sun.misc.Launcher$ExtClassLoader class in a Sun JVM.
The System Classloader (sometimes called the Application Classloader) is the classloader with which most people are the most familiar. This classloader looks for classes in the paths listed in java.class.path, which can be set with the -classpath parameter or CLASSPATH environment variable. Note, that how classes are located is platform dependent; so, using the CLASSPATH
Generally, the class with a main() method entry-point will be loaded by this Classloader.
In a Sun JVM, this classloader is implemented by the sun.misc.Launcher$AppClassLoader class.
It is possible for an application to add its own classloader to the hierarchy described earlier.
A custom classloader can load classes from any location it is designed to load from. However, the classloader must defer to its parent before attempting to load a class.
Hot deployment of code might be an example where implementing a custom classloader is needed. Another common example is a compiling classloader, which looks for modified .java files, compiles, and loads the resulting class file. There are many other examples where this might be beneficial.
However, writing your own classloader can become complex–it relies heavily on Java Reflection. Writing a custom Classloader will be the subject of a future article on Thinkmiddleware.com.
Troubleshooting Classloader issues in a Sun JVM
Adding the command-line parameter “-verbose:class” will begin printing debug output about classloader activity including which class is being loaded and where it was loaded from. The JVM will also report what JAR files are being opened.
Using the Tester example from previous posts that slowly consumes memory,
java -verbose:class Tester
the following is reported with Java 1.6.0_10 on Linux 2.6.25:
[Loaded java.lang.Object from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.io.Serializable from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.String from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.reflect.Type from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Class from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Cloneable from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.ClassLoader from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.System from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Throwable from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Error from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.ThreadDeath from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Exception from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.RuntimeException from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded Tester from file:/path/thinkmiddleware/01172009_classloaders/]
[Loaded I from file:/path/thinkmiddleware/01172009_classloaders/]
[Loaded sun.misc.Cleaner from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.lang.Enum from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded sun.misc.Signal$1 from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.util.AbstractList$Itr from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.util.HashMap$KeySet from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.util.LinkedHashMap$LinkedHashIterator from /opt/jdk1.6.0_11/jre/lib/rt.jar]
[Loaded java.util.LinkedHashMap$KeyIterator from /opt/jdk1.6.0_11/jre/lib/rt.jar]
This will generally answer the question of where a particular class was loaded from. Unfortunately, the Sun JVM does not report where which classloader loaded a particular class; to this end, printing out java.class.path and java.ext.dirs (plus, the locations like $JRE_HOME/lib/rt.jar) and some reasoning is needed–but, is possible.
In this example, java.ext.dirs is set at the default value of “$JRE_HOME/lib/ext” and java.class.path is set to “.”.
From this output, we can see that the Tester class (passed in on the command line) was loaded from the directory “file:/path/thinkmiddleware/01172009_classloaders/”–this is the working directory of the JVM. Likewise, the I class referenced by Tester was also found in “file:/path/thinkmiddleware/01172009_classloaders/”. We can further surmize that these classes were loaded by the System Classloader because they were located in a directory listed in java.class.path.
Troubleshooting Classloader issues in an IBM JVM
Essentially, the same thing can be done as seen in the Sun example in the last section.
The example above only present the simple case, where the class was loaded correctly. When things go wrong, it becomes complex to troubleshoot,quickly. Adding custom Classloaders or a J2EE Container’s Classloaders into the discussion makes the situation even more harry.
This articles focuses on a stand-alone JVM’s classloaders (absent any custom classloaders).