NoClassDefFoundError в JFace FontRegistry

Когда я запускаю приложение SWT (через профиль запуска Eclipse), я получаю следующую трассировку стека:

Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/jface/resource/FontRegistry
    at org.eclipse.jface.resource.JFaceResources.getFontRegistry(JFaceResources.java:338)
    at org.eclipse.jface.window.Window.close(Window.java:313)
    at org.eclipse.jface.dialogs.Dialog.close(Dialog.java:971)
    at org.eclipse.jface.dialogs.ProgressMonitorDialog.close(ProgressMonitorDialog.java:348)
    at org.eclipse.jface.dialogs.ProgressMonitorDialog.finishedRun(ProgressMonitorDialog.java:582)
    at org.eclipse.jface.dialogs.ProgressMonitorDialog.run(ProgressMonitorDialog.java:498)
    at com.blah.si.workflow.SWTApplication.main(SWTApplication.java:135)

Теперь о вещах, которые делают это странным:

  1. Когда я меняю путь сборки проекта и заменяю jface.jar исходным проектом (та же версия - 3.3.1), ошибка исчезает.
  2. Другие приложения, которые у меня есть, которые используют ту же банку и копию того же профиля запуска и проекта, все работают нормально.
  3. Это НЕClassNotFoundException . Класс находится в пути к классам. Если я прикреплю источник к банке, я могу выполнить отладку в методе getFontRegistry. Метод будет успешно выполнен несколько раз, прежде чем в конечном итоге будет выдан NoClassDefFoundErrorстрока 338. Строка 337 - это оператор «if variable == null», проверяющий, была ли инициализирована статическая переменная. Строка 338 инициализирует его, если он еще не инициализирован. В первый раз проверка нуля не выполняется, и выполняется инициализация. При последующих проходах через метод проверка на null проходит, и, таким образом, возвращается уже инициализированное статическое значение. На последнем проходе (тот, который завершился неудачей) проверка нуля снова терпит неудачу (даже если статическая переменная уже была инициализирована), и когда он пытается повторно инициализировать статическую переменную,NoClassDefFoundErrorброшен. Вот соответствующий источник (начиная со строки 336 обратите внимание, что fontRegistry - это частная статическая переменная, которая не устанавливается ни в каком другом месте):

.

public static FontRegistry getFontRegistry() {
   if (fontRegistry == null) {
     fontRegistry = new FontRegistry(
         "org.eclipse.jface.resource.jfacefonts");
   }
   return fontRegistry;
}

.

  1. Я уже получил новую копию jar-файла (чтобы убедиться, что он не поврежден) удалил мои файлы .classpath и .project, запустил новый проект и воссоздал профиль запуска. Без изменений.

Из-за особенностей в пункте 3 выше, я подозреваю какое-то странное поведение загрузчика классов - кажется, что последний проход через метод находится в другом загрузчике классов?

Идеи?

Обновление: ответ, предоставленный Pourquoi Litytestdata, побудил меня обратить внимание на то, что происходит в блоке try чуть выше строки 458 ProgressMonitorDialog. Действительно, этот код генерировал исключение, которое было поглощено блоком finally. Основной причиной был ДРУГОЙ отсутствующий класс (отсутствующим классом был не JFontRegistry или какой-либо из его непосредственно связанных классов, а другой, который зависел от паутины в пограничном случае.) Я поддерживаю все ответы, указывающие мне обратить внимание на путь к классам , и принятие Pourquoi's, потому что это был прорыв. Спасибо всем.

Ответов (5)

Решение

Я думаю, что трассировка стека, представленная выше, скрывает настоящую проблему. Ниже приведен код метода run в org.eclipse.jface.dialogs.ProgressMonitorDialog (с добавленным мной комментарием):

public void run(boolean fork, boolean cancelable,
         IRunnableWithProgress runnable) throws InvocationTargetException,
         InterruptedException {
     setCancelable(cancelable);
     try {
         aboutToRun();
         // Let the progress monitor know if they need to update in UI Thread
         progressMonitor.forked = fork;
         ModalContext.run(runnable, fork, getProgressMonitor(), getShell()
                 .getDisplay());
     } finally {
         finishedRun();  // this is line 498
     }
}

Вторая снизу строка в трассировке стека Джареда - это строка 498 этого класса, которая является вызовом finishedRun() внутри finally блока. Я подозреваю, что настоящая причина - исключение, возникшее в try блоке. Поскольку код в finally блоке также вызывает исключение, исходное исключение теряется.

Чтобы добавить к отличному ответу TofuBeer , поскольку NoClassDefFoundError указывает, что:

  • класс был найден самым ,org.eclipse.jface.resource.FontRegistry ClassLoader
  • но не может быть загружен, не вызывая ошибки, например, наличие статических блоков или членов, использующих объект Class, не найденный вClassLoader .

Посмотрим на org.eclipse.jface.resource.FontRegistryисходный код :

У него нет статической инициализации переменной (как и у его суперклассов).

Посмотрим на org.eclipse.jface.resource.JFaceResourcesисходный код

getFontRegistry() Функция , в которой ошибка срабатывает использует переменный статический fontRegistry :

/**
 * The JFace font registry; <code>null</code> until lazily initialized or
 * explicitly set.
 */
private static FontRegistry fontRegistry = null;

Таким образом, напрашивается поднимает на вопрос : почему статическая переменная инициализируется внезапно рассматриваться null снова?

Потому что как-нибудь FontRegistryили JFaceResourcesвыгрузится гк ?!

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

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


Если бы это был плагин eclipse , это могло быть связано с этой записью в FAQ

Вот типичный сценарий для нового пользователя:
вы пишете подключаемый модуль, расширяющий подключаемый модуль XYZ.
Чтобы заставить его скомпилировать, вы добавляете ссылку на JAR-файл для плагина XYZ в путь сборки вашего проекта либо со страницы свойств Java Build Path, либо путем редактирования файла .classpath.
При запуске среды выполнения верстака, сообщаются следующая удивительная ошибка: java.lang.NoClassDefFoundError: XYZ.SomeClass.

Не просматривайте вкладку «Плагины и фрагменты» в конфигурации запуска рабочей среды выполнения.
Эта вкладка влияет только на то, какие подключаемые модули используются для вашей рабочей среды выполнения и загружаются ли они из рабочей области или из каталога установки Eclipse.

Вместо этого начните искать в манифесте подключаемого модуля.
Отредактируйте файл plugin.xml и убедитесь, что XYZ указан как обязательный плагин .
Затем сохраните файл plugin.xml.
Это автоматически обновит путь сборки проекта.

Никогда не редактируйте файл .classpath вручную при написании подключаемого модуля.
Плагин Manifest Editor просто перезаписывает любые внесенные в него изменения. Не очень цивилизованно, но так работает.

Похоже, вам не хватает файла JAR, который содержит зависимость, как упоминалось в этой записи блога от июля 2006 года, написанной Сандживом ДЖИВАНОМ :

Разница между ClassNotFoundException и NoClassDefFoundError

A ClassNotFoundExceptionвыдается, когда сообщаемый класс не найден ClassLoader.
Обычно это означает, что класс отсутствует в CLASSPATH.
Это также может означать, что рассматриваемый класс пытается загрузить из другого класса, который был загружен в родительский, ClassLoaderи, следовательно, класс из дочернего ClassLoaderне отображается.
Иногда это происходит при работе в более сложных средах, таких как сервер приложений (WebSphere печально известна такими ClassLoaderпроблемами).

Люди часто путают java.lang.NoClassDefFoundErrorс java.lang.ClassNotFoundException. Однако есть важное различие.

Например, исключение (на самом деле ошибка, поскольку java.lang.NoClassDefFoundErrorявляется подклассом java.lang.Error), например

java.lang.NoClassDefFoundError:
org/apache/activemq/ActiveMQConnectionFactory

не означает, что класса ActiveMQConnectionFactory нет в CLASSPATH.

На самом деле все совсем наоборот.

Это означает, что класс ActiveMQConnectionFactoryбыл найден, ClassLoaderоднако при попытке загрузить класс возникла ошибка при чтении определения класса.

Обычно это происходит, когда рассматриваемый класс имеет статические блоки или члены, которые используют класс, который не найден ClassLoader.

Итак, чтобы найти виновника, просмотрите источник рассматриваемого класса ( ActiveMQConnectionFactoryв данном случае) и найдите код, используя статические блоки или статические члены .
Если у вас нет доступа к источнику, просто декомпилируйте его с помощью JAD .

Изучив код, скажем, вы нашли строку кода, как показано ниже, убедитесь, что класс SomeClass в вашем CLASSPATH.

private static SomeClass foo = new SomeClass();

Совет: Чтобы узнать, к какому jar- файлу принадлежит класс, вы можете использовать jarFinder веб-сайта . Это позволяет указать имя класса с использованием подстановочных знаков и искать класс в своей базе данных jar-файлов.
jarhoo позволяет вам делать то же самое, но его больше нельзя использовать бесплатно.

Если вы хотите определить, к какому jar-файлу принадлежит класс, на локальном пути, вы можете использовать такую ​​утилиту, как jarscan . Вы просто указываете класс, который хотите найти, и путь к корневому каталогу, в котором вы хотите начать поиск класса в файлах jar и zip.

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

try
{
    final Class       clazz;
    final ClassLoader loader;

    clazz  = Class.forName("org/eclipse/jface/resource/FontRegistry");
    loader = clazz.getClassLoader(); 
    System.out.println("The classloader at step 1 is: " + loader);
}
catch(final Throwable ex)
{
    ex.printStackTrace();
}

А затем сделайте то же самое, когда вы получаете NoClassDefFoundError, и посмотрите, отличаются ли загрузчики классов.

Тогда вы сможете убедиться, что это ClassLoader, который отличается. Вы можете сообщить, что с этим происходит? В зависимости от результата у меня может быть больше идей.

Если вы попытаетесь загрузить класс FontRegistry самостоятельно (как описано в TofoBeer), вы обнаружите, что классы следующих JAR являются зависимыми классами при использовании FontRegistry.

org.eclipse.core.commands_xxxxx.jar

Вы должны добавить этот JAR в свой путь сборки.