Как стать автором
Обновить

Диагностируем причину, выживаем в JAR hell: не дышим серой и не варимся в котле

Время на прочтение6 мин
Количество просмотров20K
Бывает что в крупном проекте работающем в jvm, внезапно обнаруживается что приложение не работает и даже не запускается при обновлении какой-либо из зависимостей проекта. Такое же возможно из-за любого другого события, которое изменило порядок следования библиотек в classpath приложения.



Лирическое отступление про причину этого явления, все до безобразия просто. В jvm до реализации проекта jigsaw «из коробки» не было возможности в одной jvm одновременно загружать несколько разных версий одного и того же класса c совпадающим именем пэкедж+класс (fully-qualified name) из разных jar файлов. Были загрузчики классов J2EE, самописные загрузчики классов или их композиция, OSGI контейнеры, но это тема для другой публикации и совсем другой мир… В итоге загружался только один из этих классов и не факт что тот который нужно со всеми вытекающими последствиями.

Итак, если случилось ваше приложение начало сыпать шквалом NoSuchMethodError/ClassNotFoundException или вести себя странно без единого изменения в исходном коде проекта. Вам поможет отчет о дублирующихся классах их загрузчиках и ресурсах, который можно сделать с помощью библиотеки jHades.

Если вы можете менять код приложения, то просто добавьте в зависимости maven артефакт org.jhades:jhades:1.0.4 и фрагмент кода
               	new org.jhades.JHades()
                    .dumpClassloaderInfo()
                    .printClasspath()
                    .overlappingJarsReport()
                    .multipleClassVersionsReport();


Вот только незадача, если вам сложно ее включить ее в сборку вашего приложения или change management требования запрещают такие изменения. В этом случае аспектно-ориентированное программирование опять спешит на помощь.

В этом примере подопытной программой остается SonarQube, как и в статьях про hawt.io/h2, логирование jdbc и CRaSH-ssh. Подробнее про процесс установки и конфигурирования сонара и агента виртуальной машины можете почитать в публикации про hawt.io/h2.

В случае с вашим приложением необходимо передать при старте jvm лишь два дополнительных параметра: -javaagent:aspectj-scripting-1.0-agent.jar указывающий на путь в файловой системе к агенту и параметр -Dorg.aspectj.weaver.loadtime.configuration=config:file:classpath.xml

Содержимое конфигурации classpath.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
    <aspects>
        <name>com.github.igorsuhorukov.JarhellInfo</name>
        <type>BEFORE</type>
        <pointcut>execution(* org.sonar.server.app.WebServer.main(..))</pointcut>
        <artifacts>
            <artifact>org.jodd:jodd:3.2</artifact>
            <classRefs>
                <variable>ClassLoaderUtil</variable>
                <className>jodd.util.ClassLoaderUtil</className>
            </classRefs>
        </artifacts>
        <process>
            <expression>
                libs = com.github.smreed.dropship.MavenClassLoader.forMavenCoordinates("org.jhades:jhades:1.0.4").getURLs();
                java.net.URLClassLoader systemClassLoader = java.lang.ClassLoader.getSystemClassLoader();
                for(lib: libs){
                    ClassLoaderUtil.addFileToClassPath(lib.getFile(), systemClassLoader);
                }
               	new org.jhades.JHades()
                    .dumpClassloaderInfo()
                    .printClasspath()
                    .overlappingJarsReport()
                    .multipleClassVersionsReport();
            </expression></process>
    </aspects>
</configuration>

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

В моем же случае приведу небольшую часть отчета:

Отчет
>> jHades — scanning classpath for overlapping jars:

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar overlaps with
file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — total overlapping classes: 342 — different classloaders.

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/aspectj-scripting-1.0-agent.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/plexus-classworlds-2.5.1.jar — total overlapping classes: 37 — same classloader! This is an ERROR!

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/stax-api-1.0-2.jar — total overlapping classes: 34 — same classloader! This is an ERROR!

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/stax-api-1.0-2.jar overlaps with
file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — total overlapping classes: 34 — different classloaders.

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/commons-collections-3.2.1.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/commons-beanutils-1.8.3.jar — total overlapping classes: 10 — same classloader! This is an ERROR!

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/tomcat-embed-core-8.0.18.jar overlaps with
file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — total overlapping classes: 8 — different classloaders.

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/ejb3-persistence-1.0.2.GA.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/tomcat-embed-core-8.0.18.jar — total overlapping classes: 6 — same classloader! This is an ERROR!

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/geronimo-spec-jta-1.0-M1.jar overlaps with
file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — total overlapping classes: 6 — different classloaders.

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/sonar-server-5.1.2.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/sonar-core-5.1.2.jar — total overlapping classes: 1 — same classloader! This is an ERROR!

file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/sonar-plugin-api-5.1.2.jar overlaps with
file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/sonar-core-5.1.2.jar — total overlapping classes: 1 — same classloader! This is an ERROR!


>> jHades multipleClassVersionsReport >> Duplicate classpath resources report:

/javax/xml/xpath/SecuritySupport$4.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 495
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 896

/javax/xml/transform/sax/SAXResult.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 971
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 1397

/org/w3c/dom/NameList.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 272
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 309

/org/w3c/dom/html/HTMLStyleElement.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 299
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 344

/javax/xml/xpath/SecuritySupport$3.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 482
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 908

/org/xml/sax/helpers/XMLReaderAdapter.class has 2 versions on these classpath locations:

sun.misc.Launcher$AppClassLoader — file:/home/igor/dev/projects/sonar-demo/sonarqube-5.1.2/lib/server/xml-apis-1.4.01.jar — class file size = 3251
Bootstrap class loader — file:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar — class file size = 5005



Более подробно с применением аспектно-ориентированного программирования и AspectJ-scripting вы можете ознакомиться в публикациях на хабре:


Иллюстрацию к этой статье взял из википедии про врата ада, но без JARов

Надеюсь, что информация из этой статьи окажется полезной вам для диагностики проблем c classpath приложения!
Теги:
Хабы:
Всего голосов 19: ↑17 и ↓2+15
Комментарии0

Публикации