【问题标题】:How static data are initialized?静态数据是如何初始化的?
【发布时间】:2015-12-31 16:36:09
【问题描述】:

“何时”有很多很好的答案,就像在这个线程中一样——When does static class initialization happen?,现在我的问题是“如何”。以下是Stephen C 的回答引述

一个类的静态初始化通常发生在紧接之前 第一次发生以下事件之一:

  • 创建了一个类的实例,
  • 调用了类的静态方法,
  • 分配了类的静态字段,
  • 使用了非常量静态字段,或者
  • 对于顶级类,执行词法嵌套在类中的断言语句。

那么它是如何在内部完成的呢?每条可以触发初始化的指令都用if?任何工作 :-) 实现的细节我都可以接受。

我用“Java”标记了这个问题,但如果我没记错的话,C# 和 Swift 也会按需初始化静态数据。

【问题讨论】:

  • 这取决于实现。我知道 Hotspot 使用诸如导致段错误之类的技巧来懒惰地检测某些条件。
  • 知道“如何”的问题在于它随时可能发生变化,因此您实际上不能依赖它。最重要的细节是初始化是线程安全的,除此之外,您冒着对可能并不总是正确的实现做出假设的风险。
  • @PeterLawrey,我很感兴趣它是如何完成的,仅此而已。
  • Segfaults -- 分段错误,或总线错误 -- 已经使用了很长时间。它们基本上是为了支持虚拟内存而开发的。每次您的操作系统加载一页虚拟内存时,它都会首先生成一个段错误。如果 VM 设置为将未使用的类指向未使用的内存,那么这将产生段错误。
  • 如果您真的感兴趣,OpenJDK 是开源的。去看看吧。

标签: java static


【解决方案1】:

正如 cmets 中提到的,这种事情可以通过 segfaults 来完成,但对于 Java,这并不是必需的。

请记住,Java 字节码不是由机器直接执行的——在它被 JIT 编译成真正的机器指令之前,它是 解释profiled 来确定何时执行编译它,这已经涉及为每个字节码指令执行大量机器指令。这段时间检查一下静态初始化的所有条件是没有问题的。

字节码也可以编译成带有检查的机器码,在检查第一次执行后重写修补。这种事情也会因为很多其他原因而发生,比如自动内联和转义分析,所以像这样进行静态初始化检查没有什么大问题。

简而言之,有很多方法,但关键是,当你运行一个 Java 程序时,除了你实际编写的代码之外,还有很多事情要做。

【讨论】:

    【解决方案2】:

    对于静态常量(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 是一个常量,而xy 是运行时值,将与静态初始化块(x *= 2)合并。

    如果您使用javap -c -p -constants Test.class 反汇编字节码,您将得到以下信息。我添加了空白行来分隔静态初始化块 (static {}) 的合并部分。

    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
    }
    

    请注意,这也表明编译器创建了一个默认构造函数,并且该构造函数调用了超类 (Object) 的默认构造函数。


    更新

    如果您将-v(详细)参数添加到javap,您将看到存储定义上述引用的值的常量池,例如对于上面列出的#15Math.random() 调用,相关常量为:

    #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
    

    如您所见,Math 类有一个 Class 常量 (#16),它被定义为字符串 "java/lang/Math"

    第一次使用#16 引用(在执行invokestatic #15 时发生),JVM 会将其解析为实际类。如果该类已经加载,它将只使用该加载的类。

    如果类尚未加载,则调用ClassLoader 加载类(loadClass()),然后调用defineClass() 方法,将字节码作为参数。在此加载过程中,通过自动分配常量值并执行先前标识的静态初始化程序代码块来初始化类。

    正是JVM执行的这个类引用解析过程触发了静态字段的初始化。这基本上就是发生的事情,但是这个过程的确切机制是特定于 JVM 实现的,例如通过 JIT(即时编译为机器代码)。

    【讨论】:

    • 非常感谢,但是如果我没看错的话,你写的是被调用方,而不是调用方,因为在 Java 代码(上面)中没有对静态数据的引用(静态初始化程序除外) .然而,你刚刚向我展示了如何检查调用方...... :-) 谢谢你的想法!
    • @greenoldman 我不是在写关于被调用者/调用者方面的文章,而是展示了生成的类文件如何包含静态字段的初始化代码,因此 JVM 可以在加载类时简单地执行该代码。 JVM 不需要复杂的初始化逻辑,因为该逻辑由类文件提供(对常量进行特殊处理)。这是“如何”初始化静态字段的答案,这就是您的问题所在。
    • 也许我没有说清楚,Java 在静态数据初始化方面(简而言之)按需工作,所以我想知道每个 SomeClass.myStaticField 引用(调用者)是否在内部用 @ 包装987654344@.
    • 更新,正如@Matt 写的 JVM 使用了相当高级的命令,我用你的代码来检查引用——引用静态数据被转换为getstatic。所以一个问题导致另一个问题:-)。
    • @greenoldman 答案已更新以解决字节码中的静态引用如何触发初始化逻辑。
    猜你喜欢
    • 1970-01-01
    • 2014-09-01
    • 2020-05-18
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-22
    相关资源
    最近更新 更多