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

По умолчанию JVM Sun лениво загружает классы и лениво инициализирует их (т. е. вызывает их <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). В JVM Sun, хотя main() содержит статическую ссылку на ClinitBomb, это происходит после A.

Я хочу, чтобы JVM загрузила и инициализировала ClinitBomb, как только она инициализирует Main (чтобы бомба взорвалась до точки A). В общем, мне нужен способ сказать: «при загрузке/ инициализируя класс X, сделайте то же самое для всех классов Y, на которые он ссылается».

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


person jon    schedule 13.12.2011    source источник
comment
Статический блок, относящийся к ClinitBomb в Main?   -  person Thorbjørn Ravn Andersen    schedule 14.12.2011
comment
@ Thorbjørn Равн Андерсен, это не решит общую проблему. Но я полагаю, что пользовательский загрузчик классов может вводить статические блоки в каждый загруженный класс.   -  person emory    schedule 14.12.2011
comment
Если не считать добавления огромного списка Class.forName(), я не знаю. ОТО, почему это так важно?   -  person Bill    schedule 14.12.2011
comment
@Bill У меня остался проект, в котором статические поля были объявлены инициализированными для регистратора для каждого класса, но для получения регистратора RMI требуется нетривиальная работа. Когда эта часть кода сломана, было бы очень приятно, чтобы статические инициализаторы выполнялись в более очевидном порядке.   -  person jon    schedule 16.12.2011
comment
Я предлагаю вам переместить инициализацию в класс и вызвать его откуда-то в начале запуска, а затем ссылаться на него позже.   -  person Bill    schedule 17.12.2011


Ответы (2)


Это невозможно сделать. 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.

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

person Tom Anderson    schedule 13.12.2011
comment
Спасибо. (Кроме того, облом. В данном случае это было бы так удобно для отладки. Я унаследовал кодовую базу, в которой большинство классов объявляют поле static final Logger, и это здорово, за исключением того, что они созданы с использованием инфраструктуры ведения журналов, которая использует RMI. Итак, , по сути, в достаточно произвольных точках выполнения у вас внезапно возникают удаленные вызовы, при сбое которых вы получаете ExceptionInInitializerError. Разве это не звучит забавно?) - person jon; 14.12.2011
comment
Хм… что интересно, класс ClinitBomb даже не загружается до тех пор, пока не будет forName(), что кажется очень странным. (То есть: удаление ClinitBomb.class по-прежнему позволяет основному принтеру A перед тем, как выдать NoClassDefFoundError.) Согласно JLS 12.1.2, это зависит от реализации; есть идеи, как изменить поведение по умолчанию в Sun JVM? - person jon; 14.12.2011
comment
Я не знаю ни одного. Запустите java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal и посмотрите, не напрашивается ли что-нибудь само собой - я ничего не видел с JRE, который у меня есть. - person Tom Anderson; 14.12.2011
comment
Похоже, что -XX:+TraceClassResolution помимо того, что выводит лог того, что во что разрешается, еще и заставляет это происходить с жадностью. С тех пор мы нашли и исправили :) ошибку, но это будет удобно в следующий раз, когда наши загрузчики классов сломаются. Спасибо! - person jon; 16.12.2011

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

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

person Joop Eggen    schedule 13.12.2011
comment
Вы можете использовать свой собственный нетерпеливый ClassLoader. - person Joop Eggen; 14.12.2011
comment
Нет. Код, который он написал, уже инициализирует ClinitBomb в вызове forName; одиночный Версия с аргументом forName эквивалентна: Class.forName(className, true, currentLoader). Он хочет, чтобы ClinitBomb было инициализировано еще раньше. - person Tom Anderson; 14.12.2011