Java 静态数据是如何初始化的?
“什么时候”有很多很好的答案,就像在这个帖子里——现在我的问题是“如何”。以下是作者对答案的引用 类的静态初始化通常在 第一次发生下列事件之一时:Java 静态数据是如何初始化的?,java,static,Java,Static,“什么时候”有很多很好的答案,就像在这个帖子里——现在我的问题是“如何”。以下是作者对答案的引用 类的静态初始化通常在 第一次发生下列事件之一时: 创建该类的一个实例 调用该类的静态方法 指定了类的静态字段 使用非恒定静态场,或 对于顶级类,执行词汇嵌套在类中的assert语句 那么它是如何在内部完成的呢?每个可能触发初始化的指令都用if包装?任何工作的详细信息:-)我都可以接受 我用“Java”来标记这个问题,但如果我没有弄错的话,C#和Swift也会根据需要初始化静态数据。对于静态常量(
- 创建该类的一个实例
- 调用该类的静态方法
- 指定了类的静态字段
- 使用非恒定静态场,或
- 对于顶级类,执行词汇嵌套在类中的assert语句
if
包装?任何工作的详细信息:-)我都可以接受
我用“Java”来标记这个问题,但如果我没有弄错的话,C#和Swift也会根据需要初始化静态数据。对于静态常量(final)字段,.class文件直接定义常量值,因此JVM可以在加载类时分配它
对于非常量静态字段,编译器会将任何初始值设定项与自定义静态初始值设定项块合并,以生成单个静态初始值设定项代码块,JVM可以在加载类时执行该代码块
例如:
public final class Test {
public static double x = Math.random();
static {
x *= 2;
}
public static final double y = myInit();
public static final double z = 3.14;
private static double myInit() {
return Math.random();
}
}
字段z
是一个常量,而x
和y
是运行时值,将与静态初始值设定项块合并(x*=2
)
如果您使用javap-c-p-constants Test.class
反汇编字节码,您将得到以下结果。我添加了空行来分隔静态初始值设定项块的合并部分(static{}
)
如您所见,Math
类有一个类常量(#16),它被定义为字符串“java/lang/Math”
第一次使用引用#16时(在执行invokestatic#15
时发生),JVM将把它解析为实际的类。如果该类已经加载,它将只使用加载的类
如果尚未加载该类,则调用以加载该类(),该类反过来调用defineClass()
方法,将字节码作为参数。在此加载过程中,通过自动分配常量值并执行先前标识的静态初始值设定项代码块,初始化该类
正是JVM执行的这个类引用解析过程触发了静态字段的初始化。基本上就是这样,但是这个过程的确切机制是特定于JVM实现的,例如通过JIT(对机器代码的即时编译)。如注释中所述,这种事情可以通过segfaults来完成,但对于Java来说,这并不是真正必要的 请记住,Java字节码不是由机器直接执行的——在它被JIT编译成真正的机器指令之前,它会被解释和分析以确定何时编译它,这已经涉及到为每个字节码指令执行大量的机器指令。在此期间检查静态初始化的所有条件没有问题 字节码也可以编译成带有检查的机器码,在第一次执行检查后重写或修补。这种事情的发生还有很多其他原因,比如自动内联和转义分析,所以像这样做静态初始化检查并没有什么大问题
简而言之,有很多方法,但关键点是,当您运行Java程序时,除了您实际编写的代码之外,还有很多方法在进行。这取决于实现。我知道Hotspot使用了一些技巧,比如引发SEGFULTS,以懒散地检测某些情况。知道“如何”的问题是,它可能随时发生变化,因此您实际上无法依赖它。最重要的细节是,初始化是线程安全的,除此之外,您可能会对可能并不总是正确的实现进行假设。@PeterLawrey,我对它是如何完成的很感兴趣,仅此而已。Segfaults——分段错误或总线错误——已经使用了很长时间。它们基本上是为了支持虚拟内存而开发的。每次操作系统加载一页虚拟内存时,它都会首先生成一个SEGFULT。如果虚拟机被设置为将未使用的类指向未使用的内存,那么这将生成一个segfault。去看一看。非常感谢,但是如果我读对了,您写的是被调用方,而不是调用方,因为在Java代码(上面)中没有对静态数据的引用(静态初始值设定项除外)。但你刚才告诉我如何检查来电端…:-)谢谢你的主意@greenoldman我没有写关于被调用方/调用方的内容,而是展示了生成的类文件如何包含静态字段的初始化代码,以便JVM可以在加载类时简单地执行该代码。JVM不需要复杂的逻辑进行初始化,因为该逻辑由类文件提供(带有常量的特殊处理)。这是“如何”初始化静态字段的答案,这就是你的问题所在。也许我没有说清楚,Java在静态数据初始化时(简而言之)是按需工作的,所以我想知道是否每个
SomeClass.myStaticField
reference(调用者)都在内部用if包装(SomeClass.alreadyInitialized…
.Update,由于@Matt编写的JVM使用了相当高的命令,我使用您的代码检查引用——引用静态数据被转换为getstatic
。因此一个问题导致另一个问题:-)更新了@greenoldman Answer,以解决字节码中的静态引用如何触发初始值设定项逻辑。
Compiled from "Test.java"
public final class test.Test {
public static double x;
public static final double y;
public static final double z = 3.14d;
static {};
Code:
0: invokestatic #15 // Method java/lang/Math.random:()D
3: putstatic #21 // Field x:D
6: getstatic #21 // Field x:D
9: ldc2_w #23 // double 2.0d
12: dmul
13: putstatic #21 // Field x:D
16: invokestatic #25 // Method myInit:()D
19: putstatic #28 // Field y:D
22: return
public test.Test();
Code:
0: aload_0
1: invokespecial #33 // Method java/lang/Object."<init>":()V
4: return
private static double myInit();
Code:
0: invokestatic #15 // Method java/lang/Math.random:()D
3: dreturn
}
#15 = Methodref #16.#18 // java/lang/Math.random:()D
#16 = Class #17 // java/lang/Math
#17 = Utf8 java/lang/Math
#18 = NameAndType #19:#20 // random:()D
#19 = Utf8 random
#20 = Utf8 ()D