Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

Constructors and Static Initializers: <init> and <clinit> in Bytecode

Recommended resources

Hello! In our last lesson, we navigated the entire class loading pipeline, from loading bytes to the final initialization phase. We concluded by noting that the JVM executes a special, compiler-generated method named <clinit> to run static initializers.

Today, we'll zoom in on that final step. You'll discover that the compiler actually creates two types of special initialization methods: one for classes (<clinit>) and one for instances (<init>). We will dissect how your Java code—constructors, field initializations, and initializer blocks—is transformed into these methods, and we'll examine the precise rules the JVM follows to invoke them. This lesson will connect the high-level Java syntax you write with the low-level bytecode the JVM executes.

The Two Faces of Initialization

At the bytecode level, initialization isn't a single event but a two-part process handled by two distinct, specially-named methods that you never call directly in your source code.

  1. <init> (Instance Initialization Method): This method is responsible for initializing a new object instance. It corresponds to what we think of as a constructor in Java.
  2. <clinit> (Class/Interface Initialization Method): This method handles the initialization of a class itself. It runs all the static initialization logic.

Let's begin with the more familiar of the two: the constructor.


<init>: The Instance Initialization Method

When you write a constructor in Java, you are defining the logic for an instance initialization method. However, the constructor body isn't the only piece of code that runs. The Java compiler collects all instance-level initialization logic and bundles it into one or more <init> methods.

This includes:

  • The code from your constructor.
  • Initializers for your instance variables (e.g., private String name = "Default";).
  • Instance initializer blocks (the ones written as { ... } directly within the class body).

The compiler cleverly merges the instance variable initializers and instance initializer blocks into every <init> method generated for the class. They are inserted right after the call to the superclass constructor (super()) and before the code from your own constructor body.

The following article from Baeldung provides a perfect illustration of this process.

and Methods in the JVM | Baeldung

This article provides a clear, code-driven explanation of how the compiler creates <init> and <clinit> methods.

Start by reading the introduction and then focus on Section 2, "Instance Initialization Methods". The Person class example is key. Pay close attention to the javap output to see how the instance field initializers ("Foo", "Bar"), the instance block (System.out.println), and the constructor body are all merged into a single <init> method.

As you saw, a single Java class can result in multiple <init> methods in the bytecode, one for each overloaded constructor you define.


Invoking <init>: The Object Creation Pattern

So, how is <init> called? Unlike a regular method, you can't just invoke it. Its invocation is a special operation, tied directly to object creation. When the compiler sees new MyClass(), it generates a standard three-instruction sequence.

  1. new: This instruction allocates memory on the heap for a new MyClass object. The object is uninitialized at this point. A reference to this raw memory is pushed onto the operand stack.
  2. dup: This instruction duplicates the reference on top of the stack. We now have two references to the same uninitialized object. Why? Because the constructor call will consume one, and we need the other one to remain so we can assign it to a variable.
  3. invokespecial: This is the instruction that actually calls the <init> method. It consumes one of the object references from the stack and uses it to invoke the appropriate instance initialization method. After this instruction completes, the object is fully initialized.

Let's watch a detailed breakdown of this pattern. The speaker clearly explains not only the pattern itself but also why invokespecial is the right instruction for the job.

Java Bytecode Crash Course

This segment from the "Java Bytecode Crash Course" by Oracle Developers provides an excellent deep dive into method invocation instructions.

First, watch the section on invocation instructions from the start of the discussion. The key takeaway here is understanding why constructors use invokespecial: they are statically linked (the compiler knows exactly which constructor to call) but still operate on an object instance (they have a this pointer). Next, watch the segment on object creation from this timestamp. This part visually walks through the new, dup, invokespecial sequence, showing how the operand stack changes at each step.

This new/dup/invokespecial triplet is a fundamental bytecode pattern you'll see every time an object is instantiated in Java.


<clinit>: The Class Initialization Method

Now let's turn our attention to the static side. The <clinit> method is the class-level counterpart to <init>. The compiler creates this single, special method to house all static initialization logic.

Specifically, the <clinit> method contains:

  • The code for initializing static fields with their declared values.
  • The code from all static { ... } blocks, executed in the order they appear in the source file.

If a class has no static fields needing initialization and no static blocks, the compiler won't generate a <clinit> method for it.

The invocation of <clinit> is handled entirely by the JVM and follows strict rules:

  • It is invoked automatically during the "Initialization" phase of class loading, which we discussed in the previous lesson.
  • It is guaranteed to be invoked only once for any given class within a class loader.
  • The JVM ensures its invocation is thread-safe. If multiple threads attempt to initialize a class at the same time, only one will execute the <clinit> method, and the others will block until it completes.

Let's return to the Baeldung article to see how this works in practice.

and Methods in the JVM | Baeldung

This section explains the role and generation of the <clinit> method.

Please read Section 3, "Class Initialization Methods". Notice how the static LOGGER field and the static block are both intended for the <clinit> method. The article correctly points out that you cannot see the invocation in the bytecode because the JVM triggers it at runtime based on the class's first active use.


A Complete Example

Let's solidify this by analyzing a class that uses all of these features. Consider the following code:

public class InitDemo {
    // 1. Static field initializer
    private static final String GREETING = "Hello";

    // 2. Static block
    static {
        System.out.println("Static block: Class is being initialized.");
    }

    // 3. Instance field initializer
    private String instanceVar = "Default";

    // 4. Instance initializer block
    {
        System.out.println("Instance block: instanceVar = " + this.instanceVar);
    }

    // 5. Constructor
    public InitDemo(String name) {
        this.instanceVar = name;
        System.out.println("Constructor: instanceVar = " + this.instanceVar);
    }

    public static void main(String[] args) {
        System.out.println("Main method started.");
        InitDemo demo = new InitDemo("World");
        System.out.println("Object created.");
    }
}

If you compile and run this, the output is:

Static block: Class is being initialized.
Main method started.
Instance block: instanceVar = Default
Constructor: instanceVar = World
Object created.

Let's trace why:

  1. The JVM starts to execute main. To do so, it must first initialize the InitDemo class.
  2. Class Initialization (<clinit>): The JVM runs <clinit>. It first initializes GREETING and then executes the static block. This prints "Static block...".
  3. The main method begins execution, printing "Main method started.".
  4. The line new InitDemo("World") is executed.
  5. Instance Initialization (<init>): The JVM executes the new/dup/invokespecial sequence to call the corresponding <init> method.
    • The invokespecial call first chains to the superclass constructor, Object.<init>().
    • The instance field initializer runs, setting instanceVar to "Default".
    • The instance initializer block runs, printing "Instance block: instanceVar = Default".
    • Finally, the constructor body runs. It re-assigns instanceVar to "World" and prints "Constructor: instanceVar = World".
  6. The main method continues, printing "Object created.".

If you were to compile this and run javap -c InitDemo, you would see two special methods: <clinit> containing the logic from steps 1 and 2, and <init> containing the logic from step 5. This disassembly provides concrete proof of how the compiler structures your initialization code.


Conclusion

In this lesson, we've peeled back another layer of the JVM's machinery to understand how Java's initialization semantics are implemented.

Here are the key takeaways:

  • The Java compiler separates initialization logic into two special methods: <init> for instances and <clinit> for classes.
  • <init> is the bytecode representation of a constructor. It bundles the constructor body, instance field initializers, and instance initializer blocks. It is invoked by the invokespecial instruction as part of the new/dup/invokespecial object creation pattern.
  • <clinit> is the bytecode representation of static initializers. It bundles static field initializers and static blocks. The JVM invokes it automatically and thread-safely exactly once when a class is first initialized.

We have seen that class loading is a sophisticated process. In our next lesson, we will investigate the actors behind this process: the class loaders themselves. We'll explore the class loader hierarchy, the parent-delegation model, and how this architecture ensures consistency and security in the Java runtime.