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>
方法)中。这意味着代码可能会出现不止一次。
这是 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>
方法)中。这意味着代码可能会出现不止一次。