5

我知道有很多关于此的主题和资源,但我想知道一个非常具体的问题(可能需要很长时间才能检查所有来源以获得明确的答案)。

我知道 JVM/Dalvik 保证当您访问类的静态字段(final static原始值除外)时,该类的静态字段已经初始化。反之亦然吗?如果我根本不访问一个类(例如,因为switch-case另一个静态方法中的代码永远不会到达某个分支),是否可以保证 VM 不会初始化这个类的静态变量?

假设我有这样的课程:

public class Boo {
      public static int[] anything = new int[] { 2,3,4 };
      private static int[] something = new int[] { 5,6,7 }; // this may be much bigger as well

      public static final int[] getAndClear() {
           int[] st = something;
           something = null;
           return st;
      }
}

我的应用程序是一个非常特殊的应用程序(在某些方面并不典型),它可能包含数百个类,例如Boo(由代码生成器生成),其中something可能是一个元素数量不同的数组(因此它可能包含很多元素,如有时好)。

根据应用程序的输入,许多这些预生成的类可能永远不会被访问。我希望很多int[]对象被不必要地初始化,占用大量内存。

4

1 回答 1

9

我知道 JVM/Dalvik 保证当您访问一个类的静态字段(最终静态原始值除外)时,该类的静态字段已经初始化。

这大部分是正确的,但由于不断内联,某些静态字段并非如此。在

class A {
  public static final String FOO = "foo";

  static { System.out.println("loaded A"); }
}

public class B {
  public static void main(String... argv) {
    System.out.println("Got " + A.FOO);
  }
}

JVM 将打印“Got foo”,但不会打印“loaded A”。事实上,即使 BA.class不在类路径上,B 也会运行,尽管在编译时至少有一个A.javaorA.class必须可用B.java


反之亦然吗?如果我根本不访问一个类(例如,因为另一个静态方法中的 switch-case 代码永远不会到达某个分支),是否可以保证 VM 不会初始化此类的静态?

是的。JLS 列出了类加载和初始化发生的确切条件,因此 JVM 实现不能自由地急切加载或初始化类。

12.4.1是感兴趣的章节。

12.4.1. 初始化发生时

类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:

  1. T 是一个类,并创建了一个 T 的实例。
  2. T 是一个类,并且调用了 T 声明的静态方法。
  3. 分配了一个由 T 声明的静态字段。
  4. 使用了由 T 声明的静态字段,并且该字段不是常量变量(第 4.12.4 节)。
  5. T 是一个顶级类(第 7.6 节),并且执行在词法上嵌套在 T(第 8.1.3 节)中的断言语句(第 14.10 节)。

“紧接在前”的措辞禁止任何急切,并规定当两个类都尝试执行上述操作之一时会发生什么——有一个与已加载但未初始化的类相关联的锁,并且都等到第一个获取该操作的线程lock 执行初始化。

“并且该字段不是常量变量(第 4.12.4 节)”措辞是我上面class B使用的规则的例外A.FOO


public static final int[] getAndClear() { ... }

可能应该是synchronized因为否则两个线程可能都获得相同的数组而不是一个接收null。我上面提到的类加载器锁不保护getAndClear.

于 2012-10-26T18:13:03.157 回答