Java的类加载性能问题及解决办法 - DZone


Java在调用 Classloader.loadclass() 时线程会被阻塞,看它的源代码。下面是 ClassLoader.loadClass() 方法的源代码摘录。如果您想查看完整的源代码java.lang.ClassLoader,可以参考这里

protected Class<?> loadClass(String name, boolean resolve)        
            throws ClassNotFoundException    
    {        
        synchronized (getClassLoadingLock(name)) {            
            // First, check if the class has already been loaded            
            Class<?> c = findLoadedClass(name);            
            if (c == null) {                
               long t0 = System.nanoTime();                
               try {                    
                   if (parent != null) {                        
                       c = parent.loadClass(name, false);                    
                   } else {                        
                       c = findBootstrapClassOrNull(name);                   
                   }                    
                   :                    
                   :  

在源代码的行中,你会看到 "

synchronized
同步 "代码块的用法。当一个代码块被同步时,只有一个线程被允许进入该代码块。在我们上面的例子中,有10个线程试图同时访问 "ClassLoader.loadClass()"。只有一个线程被允许进入同步代码块,其余9个线程将被置入阻塞状态。

下面是'getClassLoadingLock()'方法的源代码,该方法返回一个对象,在此基础上发生同步:

protected Object getClassLoadingLock(String className) {   
    Object lock = this;   
    if (parallelLockMap != null) {      
       Object newLock = new Object();      
       lock = parallelLockMap.putIfAbsent(className, newLock);      
       if (lock == null) {     
          lock = newLock;      
       }   
     }   
     return lock; 
}

注意到'getClassLoadingLock()'方法对于相同的类名每次都会返回相同的对象,即如果类名是'io.ycrash.DummyObject' - 它每次都会返回相同的对象。因此,所有的10个线程都会得到相同的对象。而在这个单一的对象上,将发生同步化。它将使所有的线程进入BLOCKED状态。

如何解决这个问题?
这个问题是由于'io.ycrash.DummyObject'类在每个循环迭代中被反复加载。这导致线程进入阻断状态。如果我们能在应用程序启动时只加载一次该类,那么这个问题就可以得到解决。这可以通过修改下面的代码来实现。

package io.ycrash.classloader; 
 
public class MyApp extends Thread { 
  
   private Class<?> myClass = initClass(); 
   
   private Class<?> initClass() { 
      
      try {         
         ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
         return classLoader.loadClass("io.ycrash.DummyObject"); 
      } catch (Exception e) {         
      }      
      
      return null
   } 
   
   @Override 
   public void run() { 
      
      while (true) { 
      
         try {            
            myClass.newInstance(); 
         } catch (Exception e) {         
         } 
      } 
   } 
   
   public static void main(String args[]) throws Exception { 
      
      for (int counter = 0; counter < 10; ++counter) { 
         
         new MyApp().start(); 
      } 
   } 
}

如果你看到现在'myClass'只一次被初始化了。不像之前的方法,每次循环迭代都要初始化myClass,现在myClass只在线程实例化时被初始化一次。
由于代码中的这一转变,"ClassLoader.loadClass() "API将不会被多次调用。因此,它将防止线程进入BLOCKED状态。

总结
如果你的应用程序也遇到了这个类加载性能问题,那么以下是解决这个问题的潜在方案:

  • 尝试看看你是否可以在应用程序启动时而不是运行时调用 "ClassLoader.loadClass() "API。
  • 如果你的应用程序在运行时反复加载同一个类,那么试着只加载一次该类。在那之后,缓存该类并重新使用它,如上例所示。
  • 使用故障排除工具,如fastThread、yCrash......来检测哪个框架或第三方库,或代码路径触发了这个问题。检查框架是否在其最新版本中给出了任何修复,如果是,就升级到最新版本。