Java Class Loading: The Basics
Introduction and Motivation
A practitioner's understanding of the Java platform cannot be considered complete without a solid understanding of the life cycle of a Java reference type (class or interface). The primary processes involved in this life cycle include loading, linking, initialization, and unloading. This paper discusses one of these processes, namely the loading process, in some detail.
Class loading is fundamental to two of the most compelling features of Java: dynamic linking and dynamic extension.
Dynamic linking allows types to be incrementally incorporated into a running JVM. Dynamic extension allows the decision as to which types are loaded into a JVM to be deferred until runtime. This means that an application hosted by the JVM can leverage types that were previously unknown, or perhaps did not even exist, when the application was compiled. Dynamic linking and extension support the notion of runtime assembly of code, a notion of critical importance to server-side containers (e.g. application servers).
A Note on Terminology
The terms "loading" and "class loading" in this article refer to the loading of reference types (classes, interfaces, and arrays). Where "class" or "type" is used, all reference types are usually implied. Explicit language will be used when this is not the case.
Type Life Cycle
Types are made available to a running program by the Java Virtual Machine. This is done through a process of loading, linking, and initialization. Loading is the process of locating the binary representation of a type and bringing it into the JVM. Linking is the process of taking the type and incorporating it into the runtime state of the JVM so that it can be executed. Initialization is the process of executing the initializers of a type (static initializers for classes; static field initializers for classes and interfaces). When a type becomes unreachable (i.e. the running application has no references to the type), it becomes eligible for garbage collection. This collection is called type unloading.
Loading a type, from a more detailed perspective, consists of three primary activities:
- Given a type's fully qualified name, produce a stream of binary data that represents that type
- Parse the stream of binary data produced in step #1 into internal structures in the method area of the JVM. The method area is a logical area of memory in the VM that is used to store information about loaded types.
- Create an instance of class
java.lang.Classthat represents the type indicated in step #1
Further details about linking, initialization, and unloading are beyond the scope of this article. Interested readers are referred to the JVM Specification for more information.
When is a Type Loaded?
This is a surprisingly tricky question to answer. This is due in large part to the significant flexibility afforded, by the JVM spec, to JVM implementations.
Loading must be performed before linking and linking must be performed before initialization. The VM spec does stipulate the timing of initialization. It strictly requires that a type be initialized on its first active use (see Appendix A for a list of what constitutes an "active use"). This means that loading (and linking) of a type MUST be performed at or before that type's first active use.
Implementations typically delay the loading of a type as long as possible. They could potentially, however, load classes much earlier. Class loaders (see below) can opt to load a type early in anticipation of eventual use. If this strategy is chosen, the class loader must not report any problem (by throwing a subclass of
java.lang.LinkageError) encountered during loading until the type's first active use. In other words, a type must appear to be loaded only when needed.
Classes are loaded so that their bytecodes can be processed by the execution engine of the JVM. All non-array (see Appendix B) reference types, including classes, are loaded either through the bootstrap class loader - sometimes referred to as the primordial class loader - or through a user-defined class loader - sometimes referred to as a custom class loader. The bootstrap class loader is an integral part of the JVM and is responsible for loading trusted classes (e.g. basic Java class library classes). User-defined class loaders, unlike the bootstrap class loader, are not intrinsic components of the JVM. They are subclasses of the
java.util.ClassLoader class that are compiled and instantiated just like any other Java class.
Different user-defined class loaders can be specialized to provide different loading policies. For example, a class loader might cache binary representations of classes and interfaces, prefetch them based on expected usage, or load a group of related classes together (as mentioned before, however, a class loader must reflect loading errors only at points in the program where they could have arisen without prefetching or group loading).
Class loaders, in addition to loading classes, can also be used to load other types of resources (e.g. localization resource bundles) from the repositories that they manage. Further treatment of this capability is outside of the scope of this article.