Статьи

ClassNotFoundException: это замедляет вашу JVM?

Большинство разработчиков Java знакомы с печально известным и очень распространенным исключением java.lang.ClassNotFoundException . Хотя источники этой проблемы, как правило, хорошо понятны (отсутствие классов / библиотек в пути к классам, проблемы с делегированием загрузчиков классов и т. Д.), Влияние на общую JVM и производительность часто неизвестно. Такая ситуация может привести к значительным последствиям для времени отклика вашего приложения и масштабируемости.

Крупные корпоративные системы Java EE с развернутыми несколькими приложениями наиболее подвержены этому типу проблем из-за большого количества различных загрузчиков классов приложений, активных во время выполнения. Это повышает риск возникновения «необнаруженного» исключения ClassNotFoundException, если только не будет выявлено явное влияние на бизнес и не будет реализован тщательный мониторинг журналов, что приводит к постоянному влиянию на производительность наряду с возможным вводом-выводом классов JVM и конкуренцией за блокировку потоков.

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

Загрузка классов Java: недостающая ссылка для оптимальной производительности

Правильное понимание этой проблемы производительности начинается с правильного знания модели загрузки классов Java. ClassNotFoundException по существу означает сбой JVM, чтобы найти и / или загрузить определенный класс Java, такой как:

  • Метод Class.forName ()
  • Метод ClassLoader.findSystemClass ()
  • Метод ClassLoader.loadClass ()

Хотя загрузка классов Java вашего приложения должна происходить только один раз в жизненном цикле JVM (или через динамические функции повторного развертывания), некоторые приложения также полагаются на операции загрузки динамического класса.

Независимо от того, повторяющиеся допустимые и «сбойные» операции загрузки классов могут быть довольно навязчивыми, особенно когда процесс загрузки предпринимается самим JDK java.lang.ClassLoader по умолчанию. Действительно, поведение JDK 1.7+ по умолчанию из-за обратной совместимости позволит загружать только один класс за раз, если загрузчик классов не помечен как «параллельный». Имейте в виду, что даже если синхронизация выполняется только на уровне класса, повторяющаяся ошибка загрузки класса с тем же именем класса все равно будет вызывать конфликт блокировки потока в зависимости от уровня параллелизма потока Java, с которым вы имеете дело. Ситуация была намного хуже назад с JDK 1.6 с синхронизацией, выполняемой систематически на уровне экземпляра загрузчика классов.

ClassNotFoundException_img1

ClassNotFoundException_img2

По этой причине контейнеры Java EE, такие как JBoss WildFly 8 , используют свои собственные внутренние параллельные загрузчики классов для загрузки классов вашего приложения. Эти загрузчики классов реализуют блокировку с более высокой степенью детализации, что позволяет одновременно загружать разные классы из одного и того же экземпляра загрузчика классов. Это также согласуется с последним улучшением JDK 1.7+, включающим поддержку многопоточных загрузчиков пользовательских классов, которые также помогают предотвратить сценарии взаимоблокировки определенных загрузчиков классов.

При этом загрузка классов классов системного уровня, таких как контейнеры java. * И контейнеры Java EE, все еще выполняется по умолчанию в JDK ClassLoader. Это означает, что повторяющаяся ошибка загрузки класса для одного и того же имени класса, например ClassNotFoundException, может по-прежнему вызывать серьезный конфликт блокировки потока. Это именно то, что мы будем тиражировать и демонстрировать для остальной части статьи.

Конфликт блокировки потока — проблема репликации

Чтобы воссоздать и смоделировать эту проблему, мы создали простое приложение согласно приведенным ниже спецификациям:

  • Веб-служба JAX-RS (REST), выполняющая Class.forName () для фиктивного имени класса, «расположенного» на уровне пакета системы:

String className = ”java.lang.WrongClassName”;

Класс. forName (className);

Симуляция по существу выполняется одновременно с 20 потоками веб-службы JAX-RS. Каждый вызов генерирует исключение ClassNotFoundException. Ведение журнала было полностью отключено, чтобы уменьшить влияние на ввод-вывод и сосредоточиться только на конкуренции за загрузку класса.

Теперь давайте посмотрим на результаты JVisualVM из нашего 30-60 секундного прогона. Мы можем ясно видеть множество BLOCKED-потоков, ожидающих блокировки на мониторе объектов.

ClassNotFoundException_img3

Анализ дампа потока JVM ясно выявляет проблему: Конфликт блокировки потока. Мы можем видеть из трассировки стека выполнения, что JBoss делегирует загрузку класса JLK ClassLoader… почему? Это потому, что наше неправильное имя класса Java обнаружено как часть пути к системному классу, например, java. *. В этой ситуации JBoss делегирует загрузку системному загрузчику классов, инициируя систематическую синхронизацию для этого конкретного имени класса и официантов из других потоков, ожидающих получения блокировки для загрузки того же имени класса.

Многие потоки ожидают получения LOCK 0x00000000ab84c0c8…

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
"default task-15" prio=6 tid=0x0000000014849800 nid=0x2050 waiting for monitor entry [0x000000001009d000]             
   java.lang.Thread.State: BLOCKED (on object monitor)                                                                 
            at java.lang.ClassLoader.loadClass(ClassLoader.java:403)                                                            
               - waiting to lock <0x00000000ab84c0c8> (a java.lang.Object)
  // Waiting to acquire a LOCK held by Thread “default task-20”                                                    
               at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)                                                    
               at java.lang.ClassLoader.loadClass(ClassLoader.java:356)    // JBoss now delegates to system ClassLoader..                                                         
               at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)                         
               at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)                                
               at java.lang.Class.forName0(Native Method)                                                                          
               at java.lang.Class.forName(Class.java:186)                                                                          
               at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)      
               at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
               at sun.reflect.GeneratedMethodAccessor15.invoke(Unknown Source)                                                      
               at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)                            
               at java.lang.reflect.Method.invoke(Method.java:601)   
……………………..

Поток Culprit — задание по умолчанию-20

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"default task-20" prio=6 tid=0x000000000e3a3000 nid=0x21d8 runnable [0x0000000010e7d000]                            
   java.lang.Thread.State: RUNNABLE                                                                                  
               at java.lang.Throwable.fillInStackTrace(Native Method)                                                            
               at java.lang.Throwable.fillInStackTrace(Throwable.java:782)                                                       
               - locked <0x00000000a09585c8> (a java.lang.ClassNotFoundException)                                                
               at java.lang.Throwable.<init>(Throwable.java:287)                                                                 
               at java.lang.Exception.<init>(Exception.java:84)                                                                  
               at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:75)                            
at java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:82) // ClassNotFoundException!                                      at java.net.URLClassLoader$1.run(URLClassLoader.java:366)                                                         
               at java.net.URLClassLoader$1.run(URLClassLoader.java:355)                                                         
               at java.security.AccessController.doPrivileged(Native Method)                                                     
               at java.net.URLClassLoader.findClass(URLClassLoader.java:354)                                                     
               at java.lang.ClassLoader.loadClass(ClassLoader.java:423)                                                          
               - locked <0x00000000ab84c0e0> (a java.lang.Object)                                                                 
               at java.lang.ClassLoader.loadClass(ClassLoader.java:410)                                                          
- locked <0x00000000ab84c0c8> (a java.lang.Object)   // java.lang.ClassLoader: LOCK acquired                                                             at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)                                                  
               at java.lang.ClassLoader.loadClass(ClassLoader.java:356)                                                          
               at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:371)                       
               at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)                              
               at java.lang.Class.forName0(Native Method)                                                                        
               at java.lang.Class.forName(Class.java:186)                                                                        
               at org.jboss.tools.examples.rest.MemberResourceRESTService.SystemCLFailure(MemberResourceRESTService.java:176)    
               at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.SystemCLFailure(Unknown Source)
…………………………………

Теперь давайте заменим наше имя класса классом Java, помеченным как часть пакета «application», и повторно запустим тест при тех же условиях загрузки.

1
2
String className = "org.ph.WrongClassName";
Class.forName(className);

ClassNotFoundException_img4

Как мы видим, мы больше не имеем дело с ЗАБЛОКИРОВАННЫМИ потоками … почему это так? Давайте посмотрим на дамп потока JVM, чтобы лучше понять это изменение поведения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"default task-51" prio=6 tid=0x000000000dd33000 nid=0x200c runnable [0x000000001d76d000]                              
   java.lang.Thread.State: RUNNABLE                                                                                   
               at java.io.WinNTFileSystem.getBooleanAttributes(Native Method)    // IO overhead due to JAR file search operation                                                  
               at java.io.File.exists(File.java:772)                                                                               
               at org.jboss.vfs.spi.RootFileSystem.exists(RootFileSystem.java:99)                                                  
               at org.jboss.vfs.VirtualFile.exists(VirtualFile.java:192)                                                           
               at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:127)                        
               at org.jboss.as.server.deployment.module.VFSResourceLoader$2.run(VFSResourceLoader.java:124)                        
               at java.security.AccessController.doPrivileged(Native Method)                                                       
               at org.jboss.as.server.deployment.module.VFSResourceLoader.getClassSpec(VFSResourceLoader.java:124)                 
               at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:252)                                   
               at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:76)                                  
               at org.jboss.modules.Module.loadModuleClass(Module.java:526)                                                        
               at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:189)   // JBoss now fully responsible to load the class                                     
               at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:444) // Unchecked since using JDK 1.7 e.g. tagged as “safe” JDK               
               at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked(ConcurrentClassLoader.java:432)                  
               at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:374)                         
               at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:119)                                
               at java.lang.Class.forName0(Native Method)                                                                           
               at java.lang.Class.forName(Class.java:186)                                                                          
               at org.jboss.tools.examples.rest.MemberResourceRESTService.AppCLFailure(MemberResourceRESTService.java:196)         
               at org.jboss.tools.examples.rest.MemberResourceRESTService$Proxy$_$$_WeldClientProxy.AppCLFailure(Unknown Source)   
               at sun.reflect.GeneratedMethodAccessor60.invoke(Unknown Source)   
……………….

Приведенная выше трассировка стека выполнения довольно показательна:

  • Поскольку не было обнаружено, что имя класса Java является частью системного пакета Java, делегирование ClassLoader не было выполнено, поэтому синхронизация не выполнялась.
  • Поскольку JBoss рассматривает JDK 1.7+ как «безопасный» JDK, был использован метод ConcurrentClassLoader .performLoadClassUnchecked (), не вызывающий блокировку монитора объекта.
  • Отсутствие синхронизации означает отсутствие конфликта блокировки потока, вызванного непрерывной ошибкой ClassNotFoundException.

По-прежнему важно отметить, что, хотя JBoss делает большую работу по предотвращению конфликта блокировок потоков в этом сценарии, попытка повторяющейся загрузки классов все равно приведет к некоторому снижению производительности из-за накладных расходов ввода-вывода, связанных с чрезмерными операциями поиска файлов JAR, опять же усиливая необходимость принятия немедленных корректирующих действий.

Заключительные слова

Я надеюсь, что вам понравилась эта статья, и теперь вы лучше понимаете потенциальное влияние на производительность из-за чрезмерной загрузки классов. Хотя JDK 1.7 и современные контейнеры Java EE принесли значительные улучшения в таких проблемах, связанных с загрузчиком классов, как взаимоблокировки и конфликты блокировок потоков, потенциальные проблемные сценарии все еще остаются. По этой причине я настоятельно рекомендую вам внимательно следить за поведением своего приложения, журналами и гарантировать, что ошибки, связанные с загрузчиком классов, такие как java.lang.ClassNotFoundException и java.lang.NoClassDefFoundError , агрессивно исправляются.

Я с нетерпением жду ваших комментариев и, пожалуйста, поделитесь опытом устранения неполадок с загрузчиками классов Java

Ссылка: ClassNotFoundException: это замедляет вашу JVM? от нашего партнера JCG Пьера Хьюга Шарбонно в блоге Шаблоны поддержки Java EE .