Что может вызвать внезапное исключение ClassNotFoundException в длительном процессе?

У нас очень маленький веб-сервис (менее 1 тыс. строк кода), который запускается Jetty. Служба всегда работала хорошо даже во время фазы стресс-тестирования. Однако после 13 дней безотказной работы в тот же день мы испытали исключение ClassNotFoundException в двух узлах.

Странная вещь заключается в том, что класс, который не был найден, уже был там (он является частью процедуры запуска и постоянно использовался для обслуживания предыдущих запросов). Фактически, просто перезапуск процесса решил проблему. Оба узла находятся в отдельных машинах и не зависят друг от друга. Они не зависят от внешних ресурсов, кроме одного JMS-соединения.

Я не смог найти нужную информацию, в то время как Googling это, так как большинство сообщаемых проблем связаны с отсутствующими классами в пути класса при запуске Java-процесса, что не является нашим делом. Мы подозреваем, что может произойти утечка памяти, которая каким-то образом повредила JVM-память, однако это не может объяснить, почему одна и та же проблема произошла в двух узлах в одно и то же время. В течение последних пяти дней мы проводили интенсивное стресс-тестирование, добавляя монитор JVM и анализатор утечки памяти, и все кажется прекрасным. Для этих тестов мы уменьшили память процесса с 2 до 512 МБ.

Детали:

  • Использование 64-разрядной серверной виртуальной машины Java HotSpot TM (сборка 16.3-b01, смешанный режим)
  • Использование jetty-runner-8.1.0.RC5.jar
  • Оригинальная строка cmd: java -Xmx2048M -jar jetty-runner-8.1.0.RC5.jar --port 5000 webapp.war
  • Intel Xeon E5-2680 8 ядер (x2) + 16 ГБ оперативной памяти
  • Red Hat Enterprise Linux 6
  • Некоторые используемые рамки: JBoss Resteasy, Spring IoC, Guava.

Можете ли вы поделиться идеями относительно того, что может заставить JVM внезапно "забыть" о существовании ранее загруженного класса, не имея возможности загрузить его снова?

Caused by: java.lang.ClassNotFoundException: com.a.b.c.SomeClass
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202) ~[na:1.6.0_37]
    at java.security.AccessController.doPrivileged(Native Method) ~[na:1.6.0_37]
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190) ~[na:1.6.0_37]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306) ~[na:1.6.0_37]
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) ~[na:1.6.0_37]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247) ~[na:1.6.0_37]
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:424) ~[na:na]
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:377) ~[na:na]
    at java.lang.Class.forName0(Native Method) ~[na:1.6.0_37]
    at java.lang.Class.forName(Class.java:247) ~[na:1.6.0_37]
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:95) ~[na:1.6.0_37]
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:107) ~[na:1.6.0_37]
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:31) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseSig(AnnotationParser.java:370) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseClassValue(AnnotationParser.java:351) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:280) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:222) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69) ~[na:1.6.0_37]
    at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52) ~[na:1.6.0_37]
    at java.lang.reflect.Field.declaredAnnotations(Field.java:1014) ~[na:1.6.0_37]
    at java.lang.reflect.Field.getDeclaredAnnotations(Field.java:1007) ~[na:1.6.0_37]

Edit:

Кто-то сказал мне, что при использовании монтирования NFS под Win может произойти, что JVM решает выгрузить класс, а затем повторно загружает его, когда это необходимо. Если в середине этого процесса соединение NFS было нарушено, дескриптор файла будет недействительным, и повторная загрузка завершится неудачно с аналогичной stacktrace. В нашем случае мы используем Linux, и все задействованные файлы находятся на одном и том же монтировании, который является локальным жестким диском. Просто для большего тестирования, я записал CD'd в временный каталог Jetty и вручную удалил один из известных для одного определенного класса сервиса. Если JVM выгружает его, а затем пытается повторно загрузить его из каталога классов, он завершит сбой. Хотя это не объясняет исходную проблему, она может добавить дополнительную информацию в таблицу...

Ответы

Ответ 1

Это то, что происходит:

  • Когда служба запускается с использованием вышеописанного cmd, Jetty создает поддиректор под "/tmp", который содержит классы и ресурсы приложений, загружаемые JVM.
  • После периода бездействия (в нашем конкретном сценарии, между 13 и 20 днями) этот каталог исчезает. В результате JVM не может загрузить файл. Мы все еще не знаем точно, если JVM выгрузил класс перед этой ошибкой или почему он попытался перечитать файл *.class. Было бы интересно посмотреть в исходный код и узнать об этом, но это не в нашем кратковременном списке ToDo.
  • Просто перезагрузка Jetty вызвала воссоздание отсутствующих каталогов, и служба снова запущена.

Хороший намек на то, что некоторые люди сообщали о подобной проблеме при загрузке ресурсов в JAR по NFS в Windows (если сетевое подключение теряется на короткое время, рутки NFS становятся недействительными, а JVM не работает с аналогичной ошибкой). Это не наш случай (/tmp - локальное хранилище), но очень похоже.

Спасибо всем за помощь.

Ответ 2

Трассировка стека говорит нам, что речь идет о обработке аннотаций и не связана с загрузкой класса для выполнения кода. Похоже, что обработчик аннотации пытается разрешить значение элемента аннотации через ClassLoader аннотированного элемента.

Другими словами, у вас есть аннотация со значением типа класса типа @Foo(xyz=ABC.class) и класс или член, аннотированный этой конструкцией, но класс ABC недоступен через ClassLoader аннотированного элемента во время выполнения.

Это не противоречит тому, что этот класс уже загружен через другой ClassLoader.