Недавно я столкнулся с проблемой при отладке интеграции с одним из сторонних приложений, которое поставляется ‘as is’ и работает на IBM WebSphere. Оно использует внутренние JMS-очереди сервера для коммуникации с нашими приложениями, развернутыми там же.
При миграции на новую версию сервера я столкнулся с NoClassDefFoundError
, возникающей в одном из классов стороннего приложения. Этот exception
генерируется при попытке создания экземпляра класса при помощи new
или при первом вызове метода класса:
Thrown if the Java Virtual Machine or a
ClassLoader
instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using thenew
expression) and no definition of the class could be found.
Были проверены все shared libraries
сервера приложений, но ошибка продолжала обваливать приложение. Проблему отложили “на потом”.
Несколько дней спустя я просматривал доклад Александра Маторина с одной Java-конференции и наткнулся на интересный кейс:
package com.lckdn.puzzler; public class Main { public static void main(String[] args) { try { Test.run(); } catch (Throwable e) { System.out.println(e); System.out.println(e.getCause()); Test.run(); } } } class Test { static { if (true) throw new RuntimeException(); } public static void run() { System.out.println("Hi!"); } } // Что будет выведено на консоль? //1. Hi! //2. RuntimeException //3. ExceptionInInitializer //4. NoClassDefFoundError
Подумайте, прежде чем ответить.
Правильный ответ – 4.
Класс Test
содержит блок статической инициализации в котором выбрасывается RuntimeException
. Компилятор не позволит сотворить такое исключение напрямую, поэтому мы обманываем его, пряча ошибку в if
. Static
метод run()
намеренно вызывается в блоке try-catch
, это позволяет поглотить первую ошибку – java.lang.ExceptionInInitializerError
. Она возникает из-за принудительной генерации RuntimeException
в static-блоке, что становится очевидным, если посмотреть в результат выполнения System.out.println(e.getCause())
. Ошибка инициализации “оборачивает” нашу ошибку. Далее снова пытаемся обратиться к классу Test
вызвав Test.run()
, но ведь он не был инициализирован! Повторная попытка приводит к выбросу java.lang.NoClassDefFoundError
.
Такое поведение при возникновении ошибки в ходе статической инициализации описано в спецификации Java SE.
Эта находка (а также частичная декомпиляция приложения:) позволила по-другому взглянуть на проблему и понять её причину. Но это уже совсем другая история 🙂