Как проанализировать NoClassDefFoundError, вызванный проигнорированным ExceptionInInitializerError?

Сегодня я провел свой день с анализом NoClassDefFoundError. После проверки пути к классу снова и снова, оказалось, что был статический член класса, который выдал Исключение, которое было проигнорировано в первый раз. После того каждого использования класса бросают NoClassDefFoundError без значимого stacktrace:

Exception in thread "main" java.lang.NoClassDefFoundError: 
    Could not initialize class InitializationProblem$A
    at InitializationProblem.main(InitializationProblem.java:19)

Это - все. Больше никаких строк.

Уменьшенный до точки, это было проблемой:

public class InitializationProblem {
    public static class A {
        static int foo = 1 / 0;
        static String getId() {
            return "42";
        }
    }

    public static void main( String[] args ) {
        try {
            new A();
        }
        catch( Error e ) {
            // ignore the initialization error
        }

        // here an Error is being thrown again,
        // without any hint what is going wrong.
        A.getId();
    }
}

Сделать его не настолько легким, все кроме последней возможности A.getId() был скрыт где-нибудь в коде инициализации очень большого проекта.

Вопрос:

Теперь, когда я нашел эту ошибку после часов метода проб и ошибок, я задаюсь вопросом, существует ли прямой способ найти эту ошибку, начинающую с вызванной исключительной ситуации. Какие-либо идеи о том, как сделать это?


Я надеюсь, что этим вопросом будет подсказка для кого-либо еще анализирующего необъяснимое NoClassDefFoundError.

9
задан tangens 5 February 2010 в 21:53
поделиться

6 ответов

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

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

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

13
ответ дан 4 December 2019 в 06:35
поделиться
QListWidget t;
t.addItem("first");
t.addItem("second");
t.item(0)->setForeground(Qt::red);
t.item(1)->setForeground(Qt::blue);
-121--2959386-

НЕ ИСПОЛЬЗУЙТЕ размытие () !

Вам не нужно сбрасывать фокус с орбиты, чтобы изменить ее вид.

В IE можно задать свойство heyFocus JS, чтобы предотвратить рисование контура. Другие браузеры разрешают переопределение с помощью свойства CSS outline .

Просто установите их в обработчике mousedown . Вы, вероятно, могли бы воспользоваться bubling события и сделать это с одного обработчика на теле:

event.srcElement && event.srcElement.hideFocus=true; // IE
event.target && event.target.style.outline='none'; // non-IE

и если вы хотите поддержать переключение между клавиатурой и мышью, в mousedown присоединить blur обработчик, который восстанавливает их по умолчанию (вам потребуется outline = " и закрытие над целью события).

-121--3018694-

Действительно, вы никогда не должны ловить ошибки, но вот как вы можете найти проблемы инициализатора где бы они не возникли.

Вот агент, который будет заставлять все исключения InInitializerErrors печатать трассировку стека при их создании:


import java.lang.instrument.*;
import javassist.*;
import java.io.*;
import java.security.*;

public class InitializerLoggingAgent implements ClassFileTransformer {
  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new InitializerLoggingAgent(), true);
  }

  private final ClassPool pool = new ClassPool(true);

  public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)  {
    try {
      if (className.equals("java/lang/ExceptionInInitializerError")) {
        CtClass klass = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtConstructor[] ctors = klass.getConstructors();
        for (int i = 0; i < ctors.length; i++) {
          ctors[i].insertAfter("this.printStackTrace();");
        }
        return klass.toBytecode();
      } else {
        return null;
      }
    } catch (Throwable t) {
      return null;
    }
  }
}

Он использует javassist для изменения классов. Скомпилировать и поместить его в файл jar с классами javassist и следующим MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent

Запустите приложение с помощью java -javaagent: agentjar.jar MainClass и все ошибки ExcepityInitialIzerError будут напечатаны даже при обнаружении.

16
ответ дан 4 December 2019 в 06:35
поделиться

Сегодня я провел свой день с анализом ошибки NoClassDefFoundError. После повторной проверки класспата выяснилось, что в классе был статический член, который бросил Исключение, которое было проигнорировано в первый раз .

Вот в чем ваша проблема! Никогда не ловите и не игнорируйте Error (или Throwable). НИКОГДА.

И если вы унаследовали какой-то сомнительный код, который может это сделать, используйте ваш любимый инструмент поиска кода / IDE, чтобы найти и уничтожить оскорбительные catch пункты.


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

Нет. Существуют сложные/героические способы... например, использование java-агента для взлома runtime-системы на лету... но не те, которые, скорее всего, есть у типичного Java-разработчика в его "инструментарии".

Вот почему вышеприведенный совет так важен.

1
ответ дан 4 December 2019 в 06:35
поделиться

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

0
ответ дан 4 December 2019 в 06:35
поделиться

Если вы когда-нибудь увидите код с этим шаблоном:

} catch(...) {
// no code
}

Узнайте, кто его написал, и ВЫБИРАЙТЕ ИХ ДЕРЬМО. Я серьезно. Попытайтесь их уволить - они не понимают отладочную часть программирования ни в каком виде, ни в какой форме.

Думаю, если они будут учениками программистов, вы можете просто выбить из них дерьмо и дать им ОДИН второй шанс.

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

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

Решение этой проблемы может занять ДНИ, если не НЕДЕЛИ. Итак, вы должны понимать, что, кодируя это, вы потенциально обойдетесь компании в десятки тысяч долларов. (Есть еще одно хорошее решение: оштрафовать их на всю потраченную зарплату из-за этой глупости - держу пари, что они больше никогда этого не сделают).

Если вы ожидаете (перехватываете) данную ошибку и обрабатываете ее, убедитесь, что:

  1. Вы знаете, что обработанная вами ошибка является ЕДИНСТВЕННЫМ ВОЗМОЖНЫМ источником этого исключения.
  2. Любые другие случайно обнаруженные исключения / причины либо повторно генерируются, либо регистрируются.
  3. Вы не улавливаете исключение (Exception или Throwable).

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

5
ответ дан 4 December 2019 в 06:35
поделиться

Единственные подсказки, которые дает ошибка, - это имя класса и то, что что-то пошло не так во время инициализации этого класса. Так что либо в одном из этих статических инициализаторов, инициализации поля или, возможно, в вызываемом конструкторе.

Вторая ошибка возникла из-за того, что класс не был инициализирован во время вызова A.getId (). Первая инициализация была прервана. Выявление этой ошибки было хорошим тестом для команды разработчиков; -)

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

1
ответ дан 4 December 2019 в 06:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: