JAVA类加载机制之Classloader以及打破加载机制的方式

在jDK1.8中,Classloader加载class的字节码到JVM,它是遵循双亲委派模型的加载机制,主要是由BootstrapClassLoader,ExtClassLoader、AppClassloader,然后是自定义的Classloader加载,这样做主要是保证JDK的内部类加载安全性,所以优先加载JDK的ClassPath的jar包的类.双亲委派模型的如下图所示,其实就是由两级父Classloader加载,分别是BootstrapClassloader和ExtClassloader。

JDK的双亲委派模型

image.png 以下是Classloader的loadClass的和核心代码:

  1. 首先获取class的name的锁,是为了防止多线程并发的加载一个类,只有一个线程去加载,
  2. findLoadedClass获取JVM已经加载的缓存中该Classloader实例是否已经加载该类, JVM底层实际是SystemDictionary,其底层是HASH表实现。
  3. 如果JVM的缓存没有加载过这个className,则先交给parent加载器加载,

如果parent为空,则由BootstrapClassloader去加载,由于BootstrapClassloader是由C++实现,java没有这个类,所以通过JNI调用JVM的函数去加载。 4. 如果还没有加载到,则子类实现的findClass方法去加载。 5. 最后判断resolve参数,是否处理class的链接。 6. 最后返回class,没有加载到,由子类抛出ClassNotFoundException异常.

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);
                }
            } 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;
    }
}
复制代码

JDK 打破双亲委派模型的两种方式

继承ClassLoader,并重写loadClass

  1. 从JVM层的缓存查询当前Classloader是否加载过。
  2. 如果没有直接从本地资源查询是否有有对应的资源,如果有,则直接调用define进行加载到JVM,返回Class.
  3. 没有本地资源没有,则从parent的Classloader加载相应的资源.
  4. 最后返回Class.
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    System.out.println("load Class: " + name);
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if(c != null){
            return c;
        }
        byte[] bt = classNameToContent.get(name);
        if (bt != null) {
            try {
                return defineClass(name, bt, 0, bt.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
           if (getParent() != null) {
              c = getParent().loadClass(name);
          }
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException thrown if class not found
            // from the non-null parent class loader
        }
        if (c != null) {
            return c;
        }
    }
    return null;
}
复制代码

使用Thread的contextClassLoader

JDK中利用线程的contextClassloader去打破双亲委派模型的例子就是Serviceloader, Serviceloader是JDK里面全称是Service Provider Interface,是实现服务提供接口,实现插件的方式,例如JDBC的Driver就是使用JDK的SPI,具体实现是适配不同的数据库,由于Driver是在rt.jar是由BootstrapClassloader加载,而接口实现类是由第三方的jar包提供,所有BootstrapClassLoader就没有办法就在,所以JDK做了一个妥协, 委派给当前线程的contextloader去加载实现类
下面是ServiceLoader的load方法,可以看出是获取当亲线程的contextClassloader去加载的接口的实现类。当前主线程类加载器默认是AppClassloader加载器加载。这样就是违反了双亲委派模型.

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
复制代码

并行类加载机制

前面介绍Classloader的抽象类中loadClass方法,加载开始时,需要获取类全名的锁, 如果用到了并行类加载机制,就会用到. 如果需要使用并行类加载机制,只有再自定义的类加载加一个静态代码块中,增加下面一行。

static {
    ClassLoader.registerAsParallelCapable();
}
复制代码

ClassLoader#registerAsParallelCapable

  1. 将自定义继承ClassLoader的类注册到ParallelLoaders的Set集合中.
protected static boolean registerAsParallelCapable() {
    Class<? extends ClassLoader> callerClass =
        Reflection.getCallerClass().asSubclass(ClassLoader.class);
    return ParallelLoaders.register(callerClass);
}
复制代码

ParallelLoaders类中其实就是维护一个Classloader实现类的Set,其中元素都是调用registerAsParallelCapable注册为并行类加载的classloader,

private static class ParallelLoaders {
    private ParallelLoaders() {}

    // the set of parallel capable loader types
    private static final Set<Class<? extends ClassLoader>> loaderTypes =
        Collections.newSetFromMap(
            new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
    static {
        synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
    }

    /**
     * Registers the given class loader type as parallel capabale.
     * Returns {@code true} is successfully registered; {@code false} if
     * loader's super class is not registered.
     */
    static boolean register(Class<? extends ClassLoader> c) {
        synchronized (loaderTypes) {
            if (loaderTypes.contains(c.getSuperclass())) {
                // register the class loader as parallel capable
                // if and only if all of its super classes are.
                // Note: given current classloading sequence, if
                // the immediate super class is parallel capable,
                // all the super classes higher up must be too.
                loaderTypes.add(c);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Returns {@code true} if the given class loader type is
     * registered as parallel capable.
     */
    static boolean isRegistered(Class<? extends ClassLoader> c) {
        synchronized (loaderTypes) {
            return loaderTypes.contains(c);
        }
    }
}
复制代码

Classloaer构造函数中,

  1. 如果Classloader注册过并行类加载器,则创建parallelLockMap的锁的HashMap,
private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        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;
    }
}
复制代码

Classloaer#getClassLoadingLock

  1. 在loadClass中获取类锁中,会判断parallelLockMap不为空,会创建一个Object对象作为这个classloader类的锁,然后放入hashMap中.

这样进行类加载过程,就synchonized锁就不是锁Classloader实例的this指针,而是Hashmap中获取加载类全名的锁,这样不同类全名就可以并行加载,这样减少了锁的粒度,提升类加载的速度.

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;
}
复制代码

总结
本文主要就JDK的阐述了双亲委派模型以及JDk中打破双亲委派的两种方式,最后介绍了JDK中实现并行类加载的实现原理.

Guess you like

Origin juejin.im/post/7049249192820080677