Core Concept: What is Class Loading?
At its heart, class loading is the process of finding the bytecode (the .class file) for a given class name and creating a java.lang.Class object from that bytecode to represent it within the JVM. This happens dynamically at runtime, not at compile time.
1. The "Phases" of Class Loading
The JVM loads a class in three distinct, sequential phases: Loading, Linking, and Initialization.
Phase 1: Loading
This is the physical act of bringing the class data into the JVM.
- What happens? The JVM finds the
.classfile (from a file system, network, JAR, etc.) and reads it into a byte array. It then creates ajava.lang.Classobject in the Method Area (a part of JVM memory) to represent this class. - Key Point: The
Classobject is the runtime representation of the class and is used by the programmer for reflection.
Phase 2: Linking
This phase prepares the loaded class for execution. It has three sub-phases:
- a) Verification (The Security Guard):
- What happens? The JVM ensures the bytecode is valid, safe, and follows the rules of the JVM specification. It checks for corrupt bytecode, final class inheritance, correct method signatures, and illegal data type conversions.
- Why? This is a critical security step that prevents many malicious attacks on the bytecode.
- b) Preparation (Memory Allocation):
- What happens? The JVM allocates memory in the Method Area for static variables of the class and initializes them to their default values (e.g.,
0,false,null). - Example: For
public static int value = 123;, the preparation phase setsvalueto0. The actual assignment of123happens in the next phase (Initialization).
- What happens? The JVM allocates memory in the Method Area for static variables of the class and initializes them to their default values (e.g.,
- c) Resolution (Replacing Names with References):
- What happens? The JVM replaces symbolic references (like names of other classes, methods, and fields) in the constant pool with direct, concrete references. It's like replacing an
#includein C with the actual code. - Note: The timing of this step is flexible. A JVM may resolve symbolic references immediately (as described) or delay it until the reference is actually used ("lazy resolution").
- What happens? The JVM replaces symbolic references (like names of other classes, methods, and fields) in the constant pool with direct, concrete references. It's like replacing an
Phase 3: Initialization
This is the final step where the class becomes ready for use.
- What happens? The JVM executes the class's static initializer blocks (
static { ... }) and assigns the correct values to all static variables. - Key Point: This is when your
static int value = 123;finally becomes123. - The JVM guarantees that initialization is performed under a lock and only once per class loader, making it thread-safe.
2. The Class Loader Hierarchy (Delegation Model)
Class loaders in Java follow a parent-delegation model. This is a crucial design for security and organization. When a class loader is asked to load a class, it doesn't try to load it itself first. Instead, it delegates the request to its parent.
The Three Built-in Class Loaders (from highest to lowest):
- Bootstrap Class Loader (Primordial):
- Parent: None. It is the root.
- Responsibility: Loads the core Java libraries (
rt.jar,charsets.jar, etc.) from the<JAVA_HOME>/jre/libdirectory. - Implementation: Written in native code (like C), so it doesn't have a Java representation.
- Platform Class Loader (Extension Class Loader in Java 8 and earlier):
- Parent: Bootstrap Class Loader.
- Responsibility: Loads classes from standard Java extension directories (like
<JAVA_HOME>/jre/lib/ext). In modern Java, it also loads platform-specific modules. - Implementation: Written in Java, a subclass of
java.lang.ClassLoader.
- System/Application Class Loader:
- Parent: Platform Class Loader.
- Responsibility: Loads classes from the application's classpath (the directories and JARs you specify with
-cpor-classpath). - Implementation: The
getSystemClassLoader()method returns this.
How the Delegation Model Works:
When the Application Class Loader receives a request to load a class (e.g., java.lang.String):
- It first delegates the request to its parent, the Platform Class Loader.
- The Platform Class Loader, in turn, delegates to its parent, the Bootstrap Class Loader.
- The Bootstrap Class Loader checks if it can load the class (i.e., if it's a core Java class).
- If it can, it loads the class, and the process stops.
- If it cannot, the request falls back to the Platform Class Loader.
- The Platform Class Loader checks if it can load the class from its extension paths.
- If it can, it loads the class.
- If it cannot, the request falls back to the Application Class Loader.
- The Application Class Loader finally attempts to load the class from the application's classpath.
Why is this model so important?
- Security: It prevents a malicious class created by a user from replacing a core Java class (like
java.lang.String). Since the Bootstrap loader loads the core classes first, a user-defined class with the same name will never be loaded. - Uniqueness: It ensures that a class is loaded only once in a specific namespace (defined by the class loader and its parents), preventing conflicts.
3. Key Principles & Rules
- Visibility Principle: A class loader can see classes loaded by its parent, but a parent cannot see classes loaded by its child.
- Uniqueness Principle: A class is uniquely identified in the JVM by its fully qualified name and the class loader instance that loaded it. This allows two different versions of the same class to coexist if loaded by different class loaders (a technique used in application servers like Tomcat).
- When is a Class Loaded? A class is loaded on its first active use. This includes:
- Using the
newkeyword (new MyClass()). - Invoking a static method.
- Accessing a static field (except for
static finalconstants, which are resolved at compile time). - Using reflection (
Class.forName("MyClass")). - Initializing a subclass (which triggers the parent class's initialization).
- Using the
4. Custom Class Loaders
You can create your own class loader by extending the java.lang.ClassLoader class and overriding the findClass(String name) method. This is useful for:
- Loading classes from non-standard sources (e.g., a database, network).
- Implementing hot-redeployment of classes in application servers.
- Creating isolated execution environments (sandboxes).
Important: A well-behaved custom class loader should follow the delegation model by first calling parent.loadClass(name) before trying to load the class itself.
Summary in a Nutshell
| Phase | Key Action | Example |
|---|---|---|
| Loading | Finds .class file and creates a Class object. | Reads MyClass.class from disk. |
| Linking | Verification: Checks bytecode validity. | Ensures no illegal bytecode. |
| Preparation: Allocates memory for statics. | static int a becomes a = 0. | |
| Resolution: Converts symbolic references. | System.out gets a direct memory link. | |
| Initialization | Executes static blocks and assignments. | static int a = 5; becomes a = 5. |
The Delegation Model ensures that core classes are protected and that class loading is a hierarchical, secure process. Understanding this mechanism is key to mastering advanced topics like Java EE, OSGi, and dynamic module systems.