Как отключить ленивую загрузку/инициализацию классов в Sun JVM?

По умолчанию Sun JVM лениво загружает классы и лениво инициализирует (т.е. вызывает их методы <clinit>). Рассмотрим следующий класс ClinitBomb, который бросает Exception во время блока static{}.

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, как только он инициализирует Main (поэтому бомба взрывается перед точкой A.) В общем, я хочу сказать: "всякий раз, когда вы загружаете/инициализируя класс X, также делают это для любых классов Y, которые он ссылается."

Есть ли способ сделать это?

Ответ 1

Нет никакого способа сделать это. JLS говорит в §12.4.1 Когда происходит инициализация (выделение мое):

Инициализация класса состоит в выполнении его статических инициализаторов и инициализаторов для статических полей, объявленных в классе. [...]

Тип класса или интерфейса T будет инициализирован непосредственно перед первым вхождением любого из следующих:

  • T - это класс и создается экземпляр T.
  • T - класс, и статический метод, объявленный T, вызывается.
  • Назначено статическое поле, объявленное T.
  • Используется статическое поле, объявленное T, и поле не является постоянной переменной (§4.12.4).
  • T - класс верхнего уровня, и выполняется инструкция assert (§14.10), лексически вложенная в T.

Вызов некоторых отражающих методов в классе Class и в пакете java.lang.reflect также вызывает инициализацию класса или интерфейса. Класс или интерфейс не будут инициализированы ни при каких других обстоятельствах.

Реализация Java, которая инициализировала классы, как только они были загружены, нарушит JLS.

Хотя вы могли бы использовать API-интерфейс JVM чтобы написать ClassFileTransformer, который добавил статический блок для каждого класса, который явно инициализировал свои ссылочные классы (возможно, через Class.forName). Как только один класс будет инициализирован, все классы, доступные из него, будут инициализированы. Это может дать вам результат, который вам нужен. Это довольно много работы, хотя!

Ответ 2

Class.forName("...", true /*initialize*/, getClassLoader());

Вы были на полпути.