Подтвердить что ты не робот

Почему NoClassDefFoundError вызвано сбоем инициализации статического поля?

Вот интересный вопрос на Java.

следующая простая java-программа содержит статическое поле, инициализированное методом статически. Фактически, я принудительно применяю метод, который вычисляет значение intiailize, чтобы поднять исключение NullPointException. Когда я получаю доступ к такому статическому полю, NoClassDefFoundError будет поднят. кажется, что VM относится к классу не полностью.

Но когда я обращаюсь к Класу, он все еще доступен;

Кто-нибудь знает, почему?

class TestClass {
    public static TestClass instance = init();

    public static TestClass init() {
       String a = null;
       a.charAt(0); //force a null point exception;
       return new TestClass();
    }
}

class MainClass {
    static public void main(String[] args) {
       accessStatic(); // a ExceptionInInitializerError raised cause by NullPointer
       accessStatic(); //now a NoClassDefFoundError occurs;

       // But the class of TestClass is still available; why?
       System.out.println("TestClass.class=" + TestClass.class);
    }

    static void accessStatic() {
        TestClass a;

        try {
            a = TestClass.instance; 
        } catch(Throwable e) {
            e.printStackTrace();
        }
    }   
}
4b9b3361

Ответ 1

Ответ на такие вопросы обычно похоронен где-то в спецификациях... (§12.4.2)

Что происходит, когда классы инициализируются:

Шаги 1-4 несколько не связаны с этим вопросом. Шаг 5 - вот что вызывает исключение:

5. Если объект класса находится в ошибочном состоянии,, то инициализация невозможна. Отпустите блокировку объекта Class и выбросите NoClassDefFoundError.

6-8 продолжить инициализацию, 8 выполняет инициализаторы, и что обычно происходит на шаге 9:

9. Если выполнение инициализаторов выполняется нормально, то заблокируйте этот объект класса, пометьте его полностью инициализированным, уведомите все ожидающие потоки, отпустите блокировку и выполните эту процедуру как обычно.

Но мы получили ошибку в инициализаторе, поэтому:

10. Иначе инициализаторы должны завершиться внезапно, выбросив некоторое исключение E. Если класс E не является ошибкой или одним из его подклассов, то создать новый экземпляр класса ExceptionInInitializerError, с E в качестве аргумента и использовать этот объект вместо E на следующем шаге. Но если новый экземпляр ExceptionInInitializerError не может быть создан из-за возникновения OutOfMemoryError, вместо этого вместо него следует использовать объект OutOfMemoryError вместо E на следующем шаге.

Да, мы видим ExceptionInInitializerError b/c исключения нулевого указателя.

11. Заблокируйте объект класса, отметьте его ошибочным, уведомите все ожидающие потоки, отпустите блокировку и выполните эту процедуру внезапно с причиной E или ее заменой, как определено на предыдущем шаге. (Из-за недостатка в некоторых ранних реализациях исключение во время инициализации класса игнорировалось, а не вызывало исключение ExceptionInInitializerError, как описано здесь.)

И тогда класс помечен как ошибочный, поэтому мы получаем исключение из шага 5 во второй раз.


Удивительная часть - это третья распечатка, которая показывает, что TestClass.class в MainClass фактически содержит ссылку на физический объект Class.

Вероятно, поскольку TestClass все еще существует, он просто помечен как ошибочный. Он уже загружен и проверен.

Ответ 2

Да, обычно это почему NoClassDefFoundError. Это злобно названо, что все. Он должен был быть назван как "исключение с ошибкой класса init" или что-то еще.

Бесполезно вводить вводящее в заблуждение имя, программисты java, которые получили эту ошибку, потратили сотни человеко-лет, пытаясь понять, почему класс не может быть найден.

Всякий раз, когда вы видите это исключение, вы должны проверить журнал вверх и попытаться выяснить основную причину, когда класс не смог инициализировать.

Ответ 3

Когда я получаю доступ к такому статическому полю, будет создан NoClassDefFoundError. кажется, что VM относится к классу не полностью.

Это правильно...

Но когда я обращаюсь к Класу, он все еще доступен

Да.

загрузчик классов не пытался удалить сломанный класс, потому что:

  • было бы трудно сделать,
  • было бы очень сложно сделать безопасно,
  • он оставил бы JVM в состоянии, когда приложение могло легко тратить много времени на повторную загрузку и перезагрузку сломанного кода, а
  • спецификации говорят (или, по крайней мере, подразумевают), что это не должно; см. другие ответы для деталей.

Чтобы войти в состояние видимости этого несоответствия, ваше приложение должно поймать ClassDefNotFoundError (или суперкласс) и попытаться восстановить его. Это хорошо документированный факт, что исключения Error обычно не подлежат восстановлению; т.е. если вы попытаетесь восстановить, JVM может оказаться в несогласованном состоянии. Вот что произошло здесь... по отношению к классам, которые были загружены/инициализированы.