类加载器并行加载类 实战及代码解析

2020-04-13 18:40:31 浏览数 (1)

类加载器是如何加载一个类的? 类加载器如何实现并行加载类? 带着这2个问题,我们看下面的内容。

类加载过程实战

首先,准备一段测试代码,代码中使用应用程序类加载器去尝试加载一个类

代码语言:javascript复制
public class ClassLoaderDemo {

    public static void main(String[] args) throws Exception {
        System.out.println();
        Class zclass =
               ClassLoader.getSystemClassLoader().loadClass("huangy.hyinterface.Generator");
        System.out.println(zclass);
    }

}

通过打断点,我们发现最开始是使用应用程序类加载器进行加载

在应用程序类加载器中,首先会尝试让其父类加载器(即扩展类加载器)去尝试加载类

在扩展类加载器中,尝试让启动类加载器去加载器类

类加载过程源码解析

上面就是标准的双亲委派模型。其具体源码解析如下:

代码语言:javascript复制
// 代码位置  java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        /*
         * 获取锁进行同步
         * 类加载器加载类是会同步的,只不过看同步的粒度是整体串行、还是分段同步
         */
        synchronized (getClassLoadingLock(name)) {

            // 首先,检查该类是否已经被加载了,加载了则直接返回
            Class<?> c = findLoadedClass(name);

            if (c == null) {

                long t0 = System.nanoTime();

                try {
                    if (parent != null) {
                        // 如果有父类加载器,首先让父类加载去加载类,即我们常说的双亲委派模型
                        c = parent.loadClass(name, false);
                    } else {
                        /*
                         * 如果没有父类加载器,则让启动类加载器进行加载
                         * 通过调试可知,扩展类加载器ExtClassLoader的parent属性为null,则会让
                         * 启动类加载器进行加载类
                         */
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 父类加载不到,从子类加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }

            // 需要解析类的话,则进行类的解析
            if (resolve) {
                resolveClass(c);
            }

            return c;
        }
    }

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;
    }

注意上述代码中,parallelLockMap只有在注册了的情况下,为不为null,也就是可以并行加载类,那么具体的注册流程是怎样的?代码如下:

代码语言:javascript复制
// 代码位置 java.lang.ClassLoader.ParallelLoaders#register

/**
 * 要显示的调用ParallelLoaders#register方法,才会进行注册。
 * 注册当前类加载器,其父类加载器必须支持并行加载,才能注册成功,这个为什么?
 * 理由很简单,因为走的是双亲委派模型,假如子类加载器支持并行,但是父类加载器是串行,那么实际上整个类加载过程就还是串行的。
 */
static boolean register(Class<? extends ClassLoader> c) {
  synchronized (loaderTypes) {
    if (loaderTypes.contains(c.getSuperclass())) {
      loaderTypes.add(c);
      return true;
    } else {
      return false;
    }
  }
}

/**
 * 判断是否支持注册
 */
static boolean isRegistered(Class<? extends ClassLoader> c) {
  synchronized (loaderTypes) {
    return loaderTypes.contains(c);
  }
}

private ClassLoader(Void unused, ClassLoader parent) {
  this.parent = parent;
  if (ParallelLoaders.isRegistered(this.getClass())) {
    // 如果注册过了,则初始化parallelLockMap,则支持并行加载
    parallelLockMap = new ConcurrentHashMap<>();
    package2certs = new ConcurrentHashMap<>();
    domains =
      Collections.synchronizedSet(new HashSet<ProtectionDomain>());
    assertionLock = new Object();
  } else {
    // no finer-grained lock; lock on the classloader instance
    parallelLockMap = null;
    package2certs = new Hashtable<>();
    domains = new HashSet<>();
    assertionLock = this;
  }
}

并行加载类实战

代码语言:javascript复制
/**
 * 并行加载类示例
 * @author huangy on 2019-10-22
 */
public class CustomClassLoaderDemo {

    public static void main(String[] args) throws Exception {

        // 自定义类加载器
        ClassLoader customClassLoader = new CustomClassLoader();

        Class zclass = customClassLoader.loadClass("huangy.hyinterface.Generator");

        System.out.println(zclass);
    }
}


class CustomClassLoader extends ClassLoader {

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 这里加载二进制字节流的方式可以自己决定
        return super.loadClass(name, resolve);
    }

    static {
        // 注册,让类加载器支持并行加载类
        ClassLoader.registerAsParallelCapable();
    }
}

如图,注册之后,parallelLockMap不为空,即获取的是类粒度的分段锁,从而实现了类的并行加载。

总结一下

  • 使用类加载器加载类,则首先会尝试让父类加载器去加载类,如果找不到,再由当前类加载器去加载类
  • 加载类的时候,默认是串行的,因为使用类加载器自身作为锁
  • 如果先进行类的注册,则能实现类的并行加载,从提高程序的启动速度

0 人点赞