Бывает что в крупном проекте работающем в jvm, внезапно обнаруживается что приложение не работает и даже не запускается при обновлении какой-либо из зависимостей проекта. Такое же возможно из-за любого другого события, которое изменило порядок следования библиотек в classpath приложения.
Лирическое отступление про причину этого явления, все до безобразия просто. В jvm до реализации проекта jigsaw «из коробки» не было возможности в одной jvm одновременно загружать несколько разных версий одного и того же класса c совпадающим именем пэкедж+класс (fully-qualified name) из разных jar файлов. Были загрузчики классов J2EE, самописные загрузчики классов или их композиция, OSGI контейнеры, но это тема для другой публикации и совсем другой мир… В итоге загружался только один из этих классов и не факт что тот который нужно со всеми вытекающими последствиями.
Итак, если случилось ваше приложение начало сыпать шквалом NoSuchMethodError/ClassNotFoundException или вести себя странно без единого изменения в исходном коде проекта. Вам поможет отчет о дублирующихся классах их загрузчиках и ресурсах, который можно сделать с помощью библиотеки jHades.
Если вы можете менять код приложения, то просто добавьте в зависимости maven артефакт org.jhades:jhades:1.0.4 и фрагмент кода
Вот только незадача, если вам сложно ее включить ее в сборку вашего приложения или 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:
Для вашей программы нужно будет изменить pointcut на соответствующую точку генерации отчета в вашей программе.
В моем же случае приведу небольшую часть отчета:
Более подробно с применением аспектно-ориентированного программирования и AspectJ-scripting вы можете ознакомиться в публикациях на хабре:
Иллюстрацию к этой статье взял из википедии про врата ада, но без JARов
Надеюсь, что информация из этой статьи окажется полезной вам для диагностики проблем c 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
…
…
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 вы можете ознакомиться в публикациях на хабре:
- Публикация логов в Elasticsearch — жизнь без регулярных выражений и без logstash
- Протоколирование JDBC запросов и их параметров в существующем приложении
- Хабр шелл: встраиваем кросплатформенный ssh server в java приложение
- Внедрение веб консолей в jvm процесс на примере SonarQube
- Доклад: «Аспектно-ориентированное программирование в распределенных системах для java разработчиков и QA»
- Напильники бывают разные или повествование про «напильник» для java программ
Иллюстрацию к этой статье взял из википедии про врата ада, но без JARов
Надеюсь, что информация из этой статьи окажется полезной вам для диагностики проблем c classpath приложения!