Java Class Loading

Last update: 2023-02-25 22:30:58 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Class loading converts a binary representation into a Class object. Linking and initialization are separate steps, thus loading a type does not imply that its static initializer has run.

Loaders and Type Identity #

The JDK supplies bootstrap, platform, and application loaders. The bootstrap loader defines core platform classes and is represented by null in selected APIs; the platform loader defines platform modules; the application loader loads application classes from the class path or module path. Custom loaders commonly use parent-first delegation:

  1. reuse an already loaded definition;
  2. ask the parent to load the binary name; then
  3. find bytes and call defineClass.

Parent-first delegation protects platform type identity. It is a convention, not an absolute rule: plugin systems sometimes use child-first loading with strict package exclusions. A runtime type is identified by both its binary name and its defining loader. Thus two loaders can define different com.example.Plugin types, and instances cannot be cast between them.

Loading, Linking, and Initialization #

The JVM specification separates the lifecycle as follows:

  1. Loading obtains bytes, creates the Class object, and associates a defining loader.
  2. Linking includes verification, preparation, and optional resolution. Verification checks structural and type-safety constraints. Preparation allocates static fields and assigns their default values. Resolution turns symbolic references into runtime references; an implementation may defer it.
  3. Initialization executes the synthesized <clinit> method, which contains static field initializers and static blocks in source order.

Compile-time constants are assigned by preparation, so reading a constant variable can avoid initialization of its declaring class. Do not infer a portable resolution schedule from an implementation’s logging output.

Exact Initialization Triggers #

JLS §12.4.1 requires initialization immediately before the first active use of a class: creating an instance, invoking a static method, assigning a static field, or reading a static field that is not a constant variable. It also includes a static assertion whose execution requires the class, selected reflective operations, and specified method-handle behavior. Class.forName initializes by default; use the three-argument overload with false to load without requesting initialization.

Before class C initializes, its superclass initializes, followed where applicable by superinterfaces that declare default methods in the order defined by the JLS. Initializing a class does not initialize arbitrary interfaces it implements. Reading Child.value initializes the class that actually declares value, not necessarily Child.

Failure and Concurrency #

The JVM serializes initialization for each class or interface. Other threads wait while one thread runs <clinit>; recursive requests by that same thread are permitted. If initialization completes abruptly, the VM records the type as erroneous, reports an initialization failure to the triggering use, and later active uses fail with NoClassDefFoundError. Restarting the process or using an independent loader is required to obtain another initialization attempt.

Static initializers should establish simple, failure-free invariants. Network calls, lock acquisition, and configuration parsing can deadlock or permanently poison a type after one transient failure.

References #

Comments

Read or comment on GitHub (sign-in required to comment)