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.