Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

JVM Class Loading Mechanism

Tech Jul 8 2

Class Loading Mechanism

The Java Virtual Machine (JVM) loads classes through a well-defined processs involving multiple phases: loading, linking, and initialization. A key aspect of this mechanism is the parent-delegation model, which governs how class loaders interact during class resolution.

Loading

During loading, the JVM locates the binary representation of a class or interface and creates an internal representation for it. This phase relies on ClassLoader instances, which follow a hierarchical delegation strategy.

Parent Delegation Model
  1. Check if already loaded: When a class loader receives a request to load a class, it first checks whether the class has already been loaded by itself or any of its ancestors. This check proceeds upward—from the current loader to the bootstrap class loader—to avoid duplicate loading.
  2. Delegate upward: If the class isn’t found, the request is delegated upward to the parent class loader. Only if all ancestors fail to locate the class does the current loader attempt to load it directly.
Example Code
public static void main(String[] args) {
    // Application ClassLoader
    System.out.println(new User().getClass().getClassLoader());

    // Extension ClassLoader
    System.out.println(new User().getClass().getClassLoader().getParent());

    // Bootstrap ClassLoader (implemented in native code; returns null)
    System.out.println(new User().getClass().getClassLoader().getParent().getParent());

    // String is loaded by Bootstrap ClassLoader → prints null
    System.out.println(String.class.getClassLoader());
}
Breaking the Parent Delegation Model

Although the parent delegation model promotes consistency and security, there are legitimate scenarios where it must be bypassed:

  1. Early JDK versions (pre-1.2): Before the parent delegation model was formalized in JDK 1.2, developers could override loadClass(). To maintain backward compatibility, JDK 1.2 preserved this method while introducing findClass() as the preferred extension point. Overriding loadClass() directly allows circumventing delegation logic.
  2. Tomcat’s Web Application Isolation: Tomcat uses custom class loaders that prioritize loading classes from a web application’s own WEB-INF/classes and WEB-INF/lib directories before delegating upward. This enables multiple applications to use different versions of the same library without conflict.
  3. SPI (Service Provider Interface) and Thread Context ClassLoader: Core Java APIs like JNDI are loaded by the bootstrap class loader but need to load provider implementations from the application classpath. Since the bootstrap loader can't access application classes, Java introduced the thread context class loader (Thread.getContextClassLoader()), which typically defaults to the application class loader. This inversion breaks the strict delegation hierarchy.
  4. OSGi Framework: OSGi implements a modular system with fine-grained class visibility and lifecycle control. Its class loaders often perform peer-to-peer lookups rather than strictly following the parent-first aproach, enabling dynamic module reloading and versioning.
  5. JDK 9+ Module System: With the introduction of the Java Platform Module System (JPMS), the extension class loader was renamed to the platform class loader. The -Xbootclasspath and java.ext.dirs options were deprecated. Core libraries are now packaged in jimage files and accessed via the JRT filesystem. During loading, the platform class loader first checks if a class belongs to a named module; if so, it loads it directly instead of delegating upward.
Mitigation Strategy

To preserve delegation integrity, developers should override findClass() instead of loadClass(). The default implementation of loadClass() already incorporates the parent delegation logic and calls findClass() only when necessary.

Linking

After loading, the class undergoes linking, which consists of three sub-phases:

  • Verification: Ensures the class file adheres to JVM specifications and doesn’t compromise security or integrity.
  • Preparation: Allocates memory for static fields and initializes them to default values (e.g., 0, false, null).
  • Resolution: Converts symbolic references (e.g., class/method names) into direct references (e.g., memory addresses).

Enitialization

The final phase executes the class’s static initializers and static field assignments explicitly defined in the source code. This occurs in a thread-safe manner the first time the class is actively used.

Common Interview Question

When is a static final field assigned its value?

The assignment timing depends on the field’s type and how it’s initialized:

  • If the field is of a primitive type or String, and is assigned a compile-time constant expression (e.g., literal or constant expression without method calls), the value is set during the preparation phase of linking.
  • Otherwise—such as when the initializer involves method calls, constructors, or non-constant expressions—the assignment occurs during the initialization phase inside the <clinit> method.
public class InitializationTest {
    public static int a = 1;                          // <clinit>
    public static final int b = 10;                   // preparation
    public static final int f = new Random().nextInt(); // <clinit>

    public static Integer c = Integer.valueOf(100);   // <clinit>
    public static final Integer d = Integer.valueOf(100); // <clinit>

    public static String s2 = "helloworld2";          // <clinit>
    public static final String s0 = "helloworld0";    // preparation
    public static final String s1 = new String("helloworld1"); // <clinit>
}

Rule of thumb: static final fields of primitive or String types are initialized during preparation only if their right-hand side is a compile-time constant. All other cases defer to the initialization phase.

Tags: JVM

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.