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); } : :
|
在源代码的行中,你会看到 "
同步 "代码块的用法。当一个代码块被同步时,只有一个线程被允许进入该代码块。在我们上面的例子中,有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......来检测哪个框架或第三方库,或代码路径触发了这个问题。检查框架是否在其最新版本中给出了任何修复,如果是,就升级到最新版本。