Статьи

Повесть о четырех JVM и одном сервере приложений

В этой статье предлагается способ выявления скрытых ошибок в приложениях 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, но это еще одна история.