这是Java中一个非常基本的应用程序,只包含一个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如何区分两个块包含的变量,因为两个块都具有相同名称和类型的变量?
答案 0 :(得分:2)
在这种情况下,您可以看到两个块仍然存在。常数10和常数20分别出现在不同的行上。然而,块是“合并”的,因为它们只是按顺序执行。既然你没有对有问题的变量做任何事情,那么两者都只写在堆栈顶部(我认为这是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
都是同一个变量。那是因为它们不是同时使用的,所以编译器只是试图提高效率,并为不同时使用的变量重用堆栈空间。
从概念上讲,两者都是不同的变量。如果稍后可以使用第一个a
的值,编译器将永远不会这样做。但实际上他们已经合并以节省空间。这只是一个简单的内存优化。
答案 1 :(得分:1)
在字节码级别,每个类只有一个静态初始化方法,名为<clinit>
。 Java级别的所有静态初始化程序都将编译为单个方法。 (这包括静态{}块和使用非常量表达式初始化的任何静态字段,即static Foo foo = bar()
)
就JVM如何区分变量而言,它没有。字节码操作的抽象级别低于Java源代码。没有局部变量,只有堆栈和可以保存值的槽表。 JVM不需要知道哪些值是执行代码的,它只是执行字节码所说的。
唯一相关的时间是您希望元数据用于调试,反射等。默认情况下,编译器将包含元数据,该元数据说明字节码中每个插槽的哪个局部变量也对应(如果有)。在这种情况下,每个槽由多个局部变量使用,因此它存储字节码的范围,在此范围内槽保存每个源级局部变量的值。
无论如何,这都是静态初始化器。根本不存在字节码级别的非静态初始值设定项。相反,所有初始值设定项都连接在一起并内联到调用超构造函数的每个构造函数(类的<init>
方法)中。这意味着代码可能会出现多次。