12

默认情况下,Sun 的 JVM 既延迟加载类,又延迟初始化(即调用它们的<clinit>方法)它们。考虑下面的类, ,它在一个块期间ClinitBomb抛出一个。Exceptionstatic{}

public class ClinitBomb {
    static {
        explode();
    }   
    private static void explode() {
        throw new RuntimeException("boom!");
    }       
}

现在,考虑如何触发炸弹:

public class Main {
    public static void main(String[] args) {
        System.out.println("A");
        try {
            Class.forName("ClinitBomb");
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
        System.out.println("B");
        ClinitBomb o2 = new ClinitBomb();
        System.out.println("C");
    }
}

我们保证爆炸发生在 B 点之前,因为forName的文档是这样说的;问题是它是否发生在点 A 之前(Main加载时)。在 Sun 的 JVM 中,即使main()包含对 的静态引用ClinitBomb,它也会发生在 A 之后。

我想要一种方法来告诉 JVM 在初始化ClinitBomb后立即加载和初始化(因此炸弹在 A 点之前Main爆炸。)一般来说,我想说“每当加载/初始化类 X 时,也对任何它引用的 Y 类。”

有没有办法做到这一点?

4

2 回答 2

10

没有办法做到这一点。JLS 在§12.4.1 When Initialization Occurs中说(强调我的):

类的初始化包括执行其静态初始化程序和类中声明的静态字段的初始化程序。[...]

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

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

在类 Class 和包 java.lang.reflect 中调用某些反射方法也会导致类或接口初始化。在任何其他情况下,类或接口都不会被初始化

一旦加载类就初始化类的 Java 实现将违反 JLS。

尽管您可以做的是使用 JVM检测 API编写一个ClassFileTransformer,它为每个明确初始化其引用类的类添加了一个静态块(可能是通过 Class.forName)。一旦一个类被初始化,所有可以从它访问的类都将被初始化。这可能会给你你想要的结果。不过,这是相当多的工作!

于 2011-12-13T23:35:46.563 回答
1
Class.forName("...", true /*initialize*/, getClassLoader());

你已经成功了一半。

于 2011-12-13T23:34:32.847 回答