В этой статье предлагается способ выявления скрытых ошибок в приложениях Java путем их запуска на разных JVM. Это иллюстрируется реальным примером использования популярного сервера приложений Java.
Отправная точка
Пользователь недавно сообщил в системе отслеживания проблем JBoss, что JBoss AS 5.0.0 GA не запускается на IBM JRE . Можно сказать: ну и что? Разве вы не слышали, что IBM JRE «лучше» подходит для другого сервера приложений?
Независимо, наша команда QA столкнулась с той же проблемой при тестировании JBoss 5.0 на новой версии Excelsior JET JVM . Быстрая проверка подтвердила, что проблема проявляется и в Oracle JRockit .
В то же время на Sun JRE все работало как шарм …
Что ж, я уважаю эталонную реализацию Sun, но кое-что подсказало мне, что другие три JVM, которые поддерживают Java 6 и прошли Sun JCK , не могут иметь точно такую же ошибку.
Дальнейшие исследования показали, что проблема заключалась в коде Java, который по счастливой случайности работал на Sun JRE.
Просто следуйте спецификации
Основной причиной проблемы является то, что JBoss 5.0 (намеренно или нет) зависит от порядка элементов в массиве, возвращаемого методом
Class.getDeclaredConstructors ()
и под Солнцем JRE порядок оказывается «правильным». Однако в спецификации Java SE API говорится: «… Элементы в возвращаемом массиве не отсортированы и не имеют определенного порядка».
Полная расшифровка анализа первопричины этого вопроса доступна в блоге этого отеля Excelsior пост . Мы также разместили комментарий к трекеру проблем JBoss.
Теперь можно сказать: мне все равно. Я развернул, развернул и разверну свое приложение с Sun JRE, что бы ни предложили другие реализации Java. ОК, нет проблем, но …
Вид магии
Рассмотрим эту простую программу, которая отражает объявленные конструкторы и выводит результат:
import java.lang.reflect.*;
class Test{
Test() {}
Test(Object o) {}
Test(String s) {}
public static void main(String args[]){
for (Constructor c: Test.class.getDeclaredConstructors())
System.out.println(c);
}
}
Если запустить на Sun JRE 6, он печатает:
Test()
Test(java.lang.Object)
Test(java.lang.String)
Все идет нормально. Похоже, что конструкторы появляются в порядке их объявления в исходном коде. Давайте добавим несколько методов экземпляра в код, чтобы сделать его немного более реалистичным:
void dont() {}
void rely() {}
void on () {}
void it () {}
и запустите программу на той же версии Sun JRE. Теперь он печатает:
Test(java.lang.String)
Test(java.lang.Object)
Test()
Сюрприз! Если вы хотите найти обоснование этого поведения, вы можете изучить источники OpenJDK, начиная с метода sort_methods () в файле hotspot \ src \ share \ vm \ runtime \ classfileparser.cpp.
Извлеченный урок: приложения Java могут не полагаться на функции JVM, которые не применяются в спецификации Java .
На умозрительных предположениях
Некоторые внимательные читатели могут заметить, что порядок конструкторов из приведенных выше примеров соответствует либо прямому, либо обратному порядку их объявления в исходном коде. Однако я бы не рекомендовал использовать это наблюдение. Просто добавьте еще один метод:
void ever() {}
к коду и запустите его, чтобы увидеть еще один (другой) порядок отраженных конструкторов!
Я должен сказать, что я не получил последний пример из моей головы. Довольно популярная библиотека JNA использовала именно это предположение относительно метода Class.getDeclaredFields (): порядок отраженных полей считался прямым или обратным. К счастью, в JNA 3.0.5 была добавлена «поддержка JVM с непредсказуемым порядком полей», хотя я бы скорее назвал это «улучшенным соответствием спецификации Java».
Вывод
Пренебрегая спецификацией Java, у вас все еще есть шанс получить работающее приложение. Но у вас также есть шанс, что он перестанет работать, если вы измените код или обновите базовую JVM. В результате вам, возможно, придется тратить (напрасно) время на поиск мелких ошибок или, что еще хуже, зацикливаться на устаревшей версии Java, если такие ошибки скрываются в сторонних компонентах (да, J2SE 1.4.2 все еще имеет «благодарность» » зрительская аудитория).
Что бы решить эту проблему? Я не могу представить себе «средство проверки правил», которое проверяет Java-приложения на соответствие спецификации Java. Реализация такого инструмента маловероятна, так как в нем задействована семантика программ.
На данный момент единственным практическим вариантом является тестирование вашего Java-приложения на разных JVM при условии, что они поддерживают одну и ту же версию Java (с одинаковым уровнем совместимости Java). Если приложение не работает с одним или несколькими из них, возможно, стоит изучить проблему.
PS Кстати, еще один большой кластер скрытых ошибок, которые может помочь вам выявить этот подход к тестированию, — это гонки данных (или случайные функции) в многопоточных приложениях Java, но это еще одна история.