Я столкнулся с проблемой загрузчика класса, которую я не понимаю. Я видел такое же поведение на OSX с Java 1.6.0 и Windows XP.
Когда я запускаю следующий код с MyListener
и MyObject
не в пути класса, я получаю a NoClassDefFoundError
. Однако, если я удаляю строку MyObject.add(my)
или заменяю ее на MyObject.add(null)
, тогда код работает нормально.
Обратите внимание, что метод с неразрешимыми зависимостями никогда не используется.
Я не понимаю, почему MyObject.add(my)
заставляет VM пытаться загрузить 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 Spec, второе издание гласит:
Единственное требование, касающееся того, когда выполняется разрешение, заключается в том, что любые ошибки, обнаруженные во время разрешения, должны быть выброшены в точку в программе, где программа выполняет какое-либо действие, которое может прямо или косвенно требовать привязки к классу или интерфейс, участвующий в ошибке
Раздел 2.17.3 Java VM Spec, второе издание гласит:
Язык программирования Java позволяет гибкость реализации при связывании действий (и из-за рекурсии, загрузки), при условии соблюдения семантики языка, что класс или интерфейс полностью проверены и подготовлены до него инициализируется и что ошибки, обнаруженные во время связывания, бросаются в точку в программе, где некоторые действия выполняются программой, которая может потребовать привязки к классу или интерфейсу, участвующим в ошибке.
и, наконец, глава 8 Inside Virtual Machine говорит:
Как описано в главе 7 "Время жизни класса", различным реализациям виртуальной машины Java разрешено выполнять разрешение в разное время во время выполнения программы. Реализация может выбрать, чтобы связать все по фронту, следуя всем символическим ссылкам из исходного класса, затем все символические ссылки из последующих классов, пока каждая символическая ссылка не будет разрешена. В этом случае приложение будет полностью связано до того, как будет вызван метод main(). Такой подход называется ранним разрешением. В качестве альтернативы, реализация может решить подождать до последней минуты, чтобы разрешить каждую символическую ссылку. В этом случае виртуальная машина Java разрешит символическую ссылку только тогда, когда она будет сначала использоваться запущенной программой. Этот подход называется поздним разрешением. Реализации могут также использовать стратегию разрешения между этими двумя крайностями.
Несмотря на то, что реализация виртуальной машины Java имеет определенную свободу выбора, когда нужно разрешать символические ссылки, каждая виртуальная машина Java должна давать внешнее впечатление о том, что использует позднее разрешение. Независимо от того, когда конкретная виртуальная машина Java выполняет свое разрешение, , она всегда будет вызывать любую ошибку, которая возникает при попытке разрешить символическую ссылку в точке выполнения программы, где символическая ссылка была фактически использована в первый раз. Таким образом, пользователь всегда будет выглядеть так, как будто разрешение было запоздалым. Если виртуальная машина Java делает раннее разрешение, и во время раннего разрешения обнаруживает, что файл класса отсутствует, он не будет сообщать о файле класса, отсутствующем, бросая соответствующую ошибку до тех пор, пока в программе не будет использоваться что-то в этом файле класса. Если класс никогда не используется программой, ошибка никогда не будет выбрана.
На первый взгляд поведение, которое я вижу, по-видимому, нарушает спецификацию JVM.