Когда Java VM пытается загрузить зависимости класса? - PullRequest
10 голосов
/ 18 ноября 2011

У меня проблема с загрузчиком классов, которую я не понимаю. Я видел такое же поведение на OSX с Java 1.6.0 и на Windows XP.

Когда я запускаю следующий код с MyListener и MyObject не в пути к классу, я получаю NoClassDefFoundError. Однако, если я удаляю строку MyObject.add(my) или заменяю ее на MyObject.add(null), тогда код работает нормально.

Обратите внимание, что метод с неразрешимыми зависимостями фактически никогда не используется.

Я не понимаю, почему MyObject.add(my) заставляет виртуальную машину пытаться загрузить MyListener, а MyListener my = new MyListener(){}; - нет.

public class Main {

    public  void neverCalled(){
        MyListener my = new MyListener(){};
        MyObject.add(my);
    }

    public static void sayHi(){
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        System.out.println("Starting...");
        sayHi();
    }
}

Нет ничего интересного в MyObject и MyListener:

public class MyObject {
    public static void add(MyListener in){}
}

public interface MyListener {}

Я провел дополнительное исследование на основе информации, предоставленной biaobiaoqi ниже. Очевидно, по какой-то неизвестной причине наличие вызова метода с параметром вызывает загрузку класса параметров, тогда как простое объявление переменной - нет.

Раздел 2.17.1 спецификации Java VM, 2-е издание гласит:

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

Раздел 2.17.3 спецификации Java VM, 2-е издание:

Язык программирования Java обеспечивает гибкость реализации в отношении того, когда происходит связывание действий (и, из-за рекурсии, загрузки), при условии, что соблюдается семантика языка, что класс или интерфейс полностью проверены и подготовлены перед ним. инициализируется, и ошибок, обнаруженных во время связывания, генерируются в той точке программы, где программа предпринимает некоторые действия, которые могут потребовать связывания с классом или интерфейсом, участвующим в ошибке .

и, наконец, глава 8 «Внутри виртуальной машины Java» гласит:

Как описано в главе 7 «Время жизни класса», различным реализациям виртуальной машины Java разрешается выполнять разрешение в разное время во время выполнения программы. Реализация может решить связать все заранее, следуя всем символическим ссылкам из начального класса, затем всем символическим ссылкам из последующих классов, пока каждая символическая ссылка не будет разрешена. В этом случае приложение будет полностью связано до того, как будет вызван его метод main (). Этот подход называется ранним разрешением. Альтернативно, реализация может выбрать ожидание до самой последней минуты, чтобы разрешить каждую символическую ссылку. В этом случае виртуальная машина Java будет разрешать символическую ссылку только тогда, когда она впервые используется запущенной программой. Этот подход называется поздним разрешением. Реализации могут также использовать стратегию разрешения между этими двумя крайностями.

Хотя реализация виртуальной машины Java имеет некоторую свободу выбора, когда разрешать символьные ссылки, каждая виртуальная машина Java должна создавать внешнее впечатление, что она использует позднее разрешение . Независимо от того, когда конкретная виртуальная машина Java выполняет свое разрешение, она всегда будет выдавать любую ошибку, которая возникает в результате попытки разрешить символическую ссылку в точке выполнения программы, где символическая ссылка фактически использовалась впервые . Таким образом, пользователю всегда будет казаться, что разрешение было поздно. Если виртуальная машина Java выполняет раннее разрешение и во время раннего разрешения обнаруживает, что файл класса отсутствует, она не сообщит об отсутствии файла класса, выдав соответствующую ошибку в программу до тех пор, пока что-то в этом файле класса фактически не используется. Если класс никогда не используется программой, ошибка никогда не будет выдана.

На первый взгляд поведение, которое я наблюдаю, похоже, нарушает спецификацию JVM.

Ответы [ 3 ]

2 голосов
/ 18 ноября 2011

Я проверю это. Когда это MyObject.add(my);, нужен только MyListener, а не MyObject. И что удивительно: когда я заменяю MyObject.add(my); на System.out.println(my);, ничего не появлялось. единственное отличие состоит в том, что тип аргумента статического метода println () - это объект, а не MyListener.

Я много искал и нашел полезную информацию. Давайте посмотрим следующие слова, это из Внутри виртуальной машины Java2

Загрузчикам классов (начальным или пользовательским) не нужно ждать первого активного использования типа, прежде чем они загрузят тип. Загрузчикам классов разрешается кэшировать двоичные представления типов, типы загрузки на ранних этапах в ожидании возможного использования или загрузки типов вместе в связанных группах. Однако, если загрузчик классов сталкивается с проблемой во время ранней загрузки, он должен сообщить об этой проблеме (выбрасывая подкласс LinkageError) только при первом активном использовании типа. Другими словами, если загрузчик классов обнаруживает отсутствующий или неправильно сформированный файл класса во время ранней загрузки, он должен дождаться сообщения об этой ошибке до первого активного использования класса программой. Если класс никогда активно не используется программой, загрузчик классов никогда не сообщит об ошибке.

Первая половина может ответить, почему существует NoClassDefFoundError. Поскольку JVM сама может решить, когда ей следует загружать класс, возможно,

    MyListener my = new MyListener(){};
    MyObject.add(my);

такой стиль просто заставляет его загружать интерфейс MyListener.

Но вторая половина кажется конфликтующей с этим. Метод neverCalled фактически никогда не вызывается, активного использования нет. Я думаю, что единственная причина может быть в том, что это спецификация java1.2.

0 голосов
/ 18 ноября 2011

Метод MyObject.add является статическим и, следовательно, должен загружаться при загрузке объекта Main, чтобы статические инициализаторы вызывались до загрузки класса Main. С другой стороны, объект MyListener может инициализировать статические инициализаторы при первом использовании, поскольку код может видеть, что в классе MyListener нет никаких вызовов GET_STATIC или PUT_STATIC.

0 голосов
/ 18 ноября 2011

Я проверил ваш код в Eclipse в одном пакете, и он отлично работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...