JVM中的方法

2025/04/02

1. 概述

JVM使用两种不同的方法来初始化对象实例和类。

在这篇简短的文章中,我们将了解编译器和运行时如何使用<init>和<clinit>方法进行初始化。

2. 实例初始化方法

让我们从一个简单的对象分配和赋值开始:

Object obj = new Object();

如果我们编译此代码片段并通过javap -c查看它的字节码,我们将看到类似以下内容:

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

为了初始化对象,JVM调用一个名为<init>的特殊方法。用JVM术语来说,这个方法就是一个实例初始化方法。当且仅当满足以下条件时,方法才是实例初始化:

  • 它是在一个类中定义
  • 它的名字是<init>
  • 它返回void

每个类都可以有0个或多个实例初始化方法,这些方法通常对应于基于JVM的编程语言(如Java或Kotlin)中的构造函数。

2.1 构造函数和实例初始化块

为了更好地理解Java编译器如何将构造函数转换为<init>,让我们考虑另一个示例:

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() {
    }
}

这是这个类的字节码:

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

尽管构造函数和初始化块在Java中是分开的,但它们在字节码级别处于相同的实例初始化方法中。事实上,这个<init>方法:

  • 首先,初始化firstName和lastName字段(索引0到13)
  • 然后,它将一些内容作为实例初始化程序块的一部分打印到控制台(索引16到21)
  • 最后,它使用构造函数参数更新实例变量

如果我们按如下方式创建一个Person:

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

然后这将转换为以下字节码:

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

这次JVM调用另一个具有与Java构造函数相对应的签名的<init>方法。

这里的关键是构造函数和其他实例初始化器等同于JVM世界中的<init>方法

3. 类初始化方法

在Java中,当我们要在类级别初始化某些东西时,静态初始化块很有用:

public class Person {

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

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

    // omitted
}

当我们编译前面的代码时,编译器将静态块转换为字节码级别的类初始化方法

简而言之,一个方法若且仅若满足以下条件,则其为类初始化方法:

  • 它的名字是<clinit>
  • 它返回 无效

因此,在Java中生成<clinit>方法的唯一方法是使用静态字段和静态块初始化器

JVM在我们第一次使用相应的类时调用<clinit>。因此,<clinit>调用发生在运行时,我们在字节码级别看不到该调用。

4. 总结

在这篇简短的文章中,我们了解了JVM中<init>和<clinit>方法之间的区别。<init>方法用于初始化对象实例;此外,JVM会在必要时调用<clinit>方法来初始化类。

为了更好地理解初始化在JVM中的工作方式,强烈建议阅读JVM规范

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章