静态数据如何初始化?

时间:2015-12-31 16:36:09

标签: java static

"和#34;有很多好的答案,就像在这个帖子中一样 - When does static class initialization happen?现在我的问题是""。以下是Stephen C

的答案引用
  

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

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

那么它是如何在内部完成的?可以触发初始化的每条指令都包含if?任何工作 :-)实施的详细信息都适合我。

我用" Java"标记问题但如果我没弄错,C#和Swift也会根据需要初始化静态数据。

2 个答案:

答案 0 :(得分:2)

正如评论中所提到的,这种事情可以通过segfaults来完成,但是使用Java这并不是必需的。

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

字节码也可以编译成带有检查的机器代码,在首次执行检查后,重写打补丁。出于许多其他原因(例如自动内联和转义分析),这种情况也会发生,所以进行这样的静态初始化检查不是什么大问题。

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

答案 1 :(得分:1)

对于静态常量(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,您将看到存储定义上面列出的引用的值的常量池,例如:对于Math.random()调用(上面列为#15),相关常量为:

#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类有一个类常量(#16),定义为字符串"java/lang/Math"

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

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

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