Java 字节码中的多个静态块发生了什么?

What is happening with more than one static block in Java byte-code?

这是 Java 中的一个非常基本的应用程序,仅包含一个 class。在那个 class 中,有一个 main 方法和两个 static 块。

class Test {
    public static void main(String args[]) {
        System.out.println("Main");
    }

    static {
        int a = 10;
    }

    static {
        int a = 20;
    }
}

这是编译这个应用程序产生的字节码。我不明白静态块发生了什么:

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: bipush        10
         2: istore_0
         3: bipush        20
         5: istore_0
         6: return
      LineNumberTable:
        line 34: 0
        line 37: 3
        line 38: 6

我的问题是:第二个静态块在哪里?如果它们合并,那么 JVM 如何区分两个块包含的变量,因为两个块具有相同名称和类型的变量?

在这种情况下,您可以看到两个块仍然存在。常量 10 和常量 20 分别出现在不同的行上。然而,从它们只是按顺序执行的意义上说,这些块是 "merged"。由于您不对相关变量执行任何操作,因此两者都被写入堆栈顶部(我认为这就是 istore_0 所做的)然后被忽略。

Code:
  stack=1, locals=1, args_size=0
     0: bipush        10
     2: istore_0
     3: bipush        20
     5: istore_0
     6: return

编辑:istore_0 将值存储到局部变量。 a 都是同一个变量。那是因为它们没有同时使用,所以编译器只是试图提高效率并为没有同时使用的变量重用堆栈 space。

从概念上讲,两者是不同的变量。如果以后可以使用第一个 a 的值,编译器将永远不会这样做。但实际上它们已被合并以保存 space。这只是一个简单的内存优化。

在字节码级别,每个 class 只有一个静态初始化方法,名为 <clinit>。 Java 级别的所有静态初始值设定项都将编译为一个方法。 (这包括 static{} 块和任何用非常量表达式初始化的静态字段,即 static Foo foo = bar()

就 JVM 如何区分变量而言,它没有。字节码在比 Java 源代码更低的抽象级别上运行。没有局部变量,只有一个堆栈和一个 table 个可以保存值的槽。 JVM 不需要知道执行代码的值是什么,它只是执行字节码所说的。

唯一相关的情况是您需要用于调试、反射等的元数据。默认情况下,编译器将包含元数据,说明字节码中每个槽对应的局部变量(如果有的话)。在这种情况下,每个槽都被多个局部变量使用,因此它存储字节码的范围,在此期间槽保存每个源代码级局部变量的值。

无论如何,这就是静态初始化程序的全部内容。非静态初始值设定项根本不存在于字节码级别。相反,所有初始化程序都被连接并内联到调用超构造函数的每个构造函数(class 的 <init> 方法)中。这意味着代码可能会出现不止一次。