У меня проблема с загрузчиком классов, которую я не понимаю. Я видел такое же поведение на 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.