Java Class Loading: The Basics, Page 2
A delegation model for class loaders was introduced in Java 2 (starting with version 1.2). In this model, class loaders are arranged hierarchically in a tree, with the bootstrap class loader as the root of the tree. Each user-defined class loader is assigned a "parent" class loader when it is constructed (the bootstrap class loader is the only class loader in the system without a parent). By default, this parent is the system class loader (see below). Alternately, a parent class loader can be provided explicitly as a construction parameter.
When a load request is made of a user-defined class loader, that class loader can either immediately attempt to load the class itself, or it can first delegate to some other class loader - only attempting to load the class itself if the delegate fails to do so. A class loader that has been delegated, provided it is not the bootstrap class loader, has the same choice - attempt to load the class itself or delegate to yet another class loader. If a class loader is eventually successful at loading the type, it will be marked as the defining class loader for the type. All of the class loaders that were given an opportunity to load the type are marked as initiating loaders of the type (see Appendix C). If no class loader is successful in loading the type, a
java.lang.ClassNotFoundException (or, in certain cases, a
java.lang.NoClassDefFoundError) will be thrown.
Typical Default Class Loader Hierarchy
By default, a Java 2 JVM typically provides a bootstrap class loader and two user- defined class loaders: the extension class loader and the system (or application) class loader. The bootstrap class loader, as mentioned above, is responsible for loading the core Java classes (e.g.
javax.*, etc.) into the VM. The extension class loader is responsible for loading classes from the JRE's extension directories. Finally, the system class loader is responsible for loading classes from the system class path.
A loaded class in a JVM is identified by its fully qualified name and its defining class loader - this is sometimes referred to as the runtime identity of the class. Consequently, each class loader in the JVM can be said to define its own namespace. Within a namespace, all fully- qualified names are unique. Two different name spaces, however, can contain identical fully-qualified names. Because the defining class loader is part of the runtime identity of the class, the classes associated with these names are considered distinct (e.g. class
x.y.z.Foo defined by class loader A is NOT considered by the JVM to be the same class as class
x.y.z.Foo defined by class loader B).
Ensuring type safe linkage in the presence of multiple class loaders (some of which are user-defined) requires special care. In fact, prior to Java 2, Java was not type-safe (see "Java is not type-safe" by V.Saraswat). To ensure type safety (i.e. type consistency across namespaces), the JVM now imposes loading constraints. A loading constraint indicates that a name in one namespace MUST refer to the same type data in the method area as the same name in another namespace. The JVM will, at prescribed times, add new constraints. This is generally done when the JVM encounters references to types whose loading was not initiated by the same class loader that initiated loading of the referencing type. When the JVM attempts to resolve a symbolic reference (during the linking phase of the type life cycle), it must first check that all of the current loading constraints are satisfied. If any of the constraints are violated, a
LinkageError will be thrown.
The Java class loader architecture affords a Java developer a tremendous amount of flexibility in the way that an application is assembled and extended. To truly leverage this flexibility, a developer must understand class loading in some detail. This article presented some of the basic concepts and notions in this area. The next article in this series will provide information about customizing the class loading process, including details on developing user-defined class loaders.
"The Java Virtual Machine Specification, Second Edition" by T.Lindholm and F.Yellin
"Inside the Java 2 Virtual Machine" by B.Venners
"Dynamic Class Loading in the Java Virtual Machine" by S.Liang and G.Bracha
Appendix A: Active Uses
The following activities qualify as active uses; all other uses are considered passive uses:
- Creation of a new instance of a class (e.g. via execution of a
newinstruction, implicit creation of a
String, reflection, cloning, deserialization}
- Invocation of a class method (i.e. a static method) declared by a class
- Use or assignment of a non-constant static field declared by a class or interface
- Invocation of certain reflective methods in the Java API, such as methods in
java.lang.Classor in classes in the
- Initialization of a subclass of a class (initialization of a class requires prior initialization of its superclass)
- Designation of a class as the initial class (with
main()) when the JVM is started
Appendix B: Loading Array Classes
NOTE: Array classes are created by the Java Virtual Machine rather than by a class loader. The JVM will, however, assign a defining class loader to each array class. An array of reference types will be assigned the defining class loader of the component type (e.g.
Foo would be assigned the defining class loader of
Foo). The JVM will assign the bootstrap class loader as the defining class loader of an array of primitives.
Appendix C: Initiating and Defining Class Loaders
Because of the class loader delegation model, the class loader that initiates the loading of a type is not necessarily the class loader that defines that type. The class loader responsible for defining a type is referred to as that type's defining class loader. Any class loader that initiates the loading of a type, directly or indirectly (due to delegation), is referred to as an initiating class loader of the that type. Note that the defining class loader of a type is also an initiating class loader for that type.
About the Author
Brandon E Taylor is a software architect in the Network Storage division of Sun Microsystem, Inc. He has worked for Sun since 1998 and has been developing "serious" software using Java since 1996. (By the way...the "E" stands for "Eugene")
Page 2 of 2