Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

The JVM uses two distinctive methods to initialize object instances and classes.

In this quick article, we're going to see how the compiler and runtime use the <init> and <clinit> methods for initialization purposes.

2. Instance Initialization Methods

Let's start with a straightforward object allocation and assignment:

Object obj = new Object();

If we compile this snippet and take a look at its bytecode via javap -c, we'll see something like:

0: new           #2      // class java/lang/Object
3: dup
4: invokespecial #1      // Method java/lang/Object."<init>":()V
7: astore_1

To initialize the object, the JVM calls a special method named <init>. In JVM jargon, this method is an instance initialization method. A method is an instance initialization if and only if:

  • It is defined in a class
  • Its name is <init>
  • It returns void

Each class can have zero or more instance initialization methods. These methods usually are corresponding to constructors in JVM-based programming languages such as Java or Kotlin.

2.1. Constructors and Instance Initializer Blocks

To better understand how the Java compiler translates constructors to <init>, let's consider another example:

public class Person {
    
    private String firstName = "Foo"; // <init>
    private String lastName = "Bar"; // <init>
    
    // <init>
    {
        System.out.println("Initializing...");
    }

    // <init>
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    // <init>
    public Person() {
    }
}

This is the bytecode for this class:

public Person(java.lang.String, java.lang.String);
  Code:
     0: aload_0
     1: invokespecial #1       // Method java/lang/Object."<init>":()V
     4: aload_0
     5: ldc           #7       // String Foo
     7: putfield      #9       // Field firstName:Ljava/lang/String;
    10: aload_0
    11: ldc           #15      // String Bar
    13: putfield      #17      // Field lastName:Ljava/lang/String;
    16: getstatic     #20      // Field java/lang/System.out:Ljava/io/PrintStream;
    19: ldc           #26      // String Initializing...
    21: invokevirtual #28      // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    24: aload_0
    25: aload_1
    26: putfield      #9       // Field firstName:Ljava/lang/String;
    29: aload_0
    30: aload_2
    31: putfield      #17      // Field lastName:Ljava/lang/String;
    34: return

Even though the constructor and the initializer blocks are separate in Java, they are in the same instance initialization method at the bytecode level. As a matter of fact, this <init> method:

  • First, initializes the firstName and lastName fields (index 0 through 13)
  • Then, it prints something to the console as part of the instance initializer block (index 16 through 21)
  • And finally, it updates the instance variables with the constructor arguments

If we create a Person as follows:

Person person = new Person("Brian", "Goetz");

Then this translates to the following bytecode:

0: new           #7        // class Person
3: dup
4: ldc           #9        // String Brian
6: ldc           #11       // String Goetz
8: invokespecial #13       // Method Person."<init>":(Ljava/lang/String;Ljava/lang/String;)V
11: astore_1

This time JVM calls another <init> method with a signature corresponding to the Java constructor.

The key takeaway here is that the constructors and other instance initializers are equivalent to the <init> method in the JVM world.

3. Class Initialization Methods

In Java, static initializer blocks are useful when we're going to initialize something at the class level:

public class Person {

    private static final Logger LOGGER = LoggerFactory.getLogger(Person.class); // <clinit>

    // <clinit>
    static {
        System.out.println("Static Initializing...");
    }

    // omitted
}

When we compile the preceding code, the compiler translates the static block to a class initialization method at the bytecode level.

Put simply, a method is a class initialization one if and only if:

  • Its name is <clinit>
  • It returns void

Therefore, the only way to generate a <clinit> method in Java is to use static fields and static block initializers.

JVM invokes the <clinit> the first time we use the corresponding class. Therefore, the <clinit> invocation happens at runtime, and we can't see the invocation at the bytecode level.

4. Conclusion

In this quick article, we saw the difference between <init> and <clinit> methods in the JVM. The <init> method is used to initialize object instances.  Also, the JVM invokes the <clinit> method to initialize a class whenever necessary.

To better understand how initialization works in the JVM, it's highly recommended to read the JVM specification.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!