深入理解ClassLoader

是什么?

相信大家在准备面试的时候都会背过Java的类加载机制,双亲委派模型。

就是当JVM收到一个类加载请求的时候,当前的类加载器首先会将类加载器传递给它的父类,父类尝试加载,如果失败继续向上传递,如果所有的父类都无法加载,则当前类加载器自己处理。

那这段话理解起来还是比较容易的,但是具体是如何做的呢?下面进行分析。

为什么?

那为什么要使用双亲委派模型吗?当前类加载器自己处理不行吗?

如果我们自己写了一个java.lang.Object类的话,那当前的类加载器直接选择加载的话,那加载的类中就会有两个一样的Object类。应用程序将会变的非常混乱。

怎么做?

下面从源码开始看Java是如何进行类加载的。

ClassLoader

介绍

先进入ClassLoader类中public abstract class ClassLoader, 可以先看一下doc,介绍了ClassLoader这个类,

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.

大意就是说"一个class loader是一个负责加载class的对象。ClassLoader是一个抽象类。给定一个类的二进制名称,一个class loader应该试图去定位或者生成构成类定义的数据。一个典型的策略是转换类名称为文件名称,然后从文件系统上读取一个叫这个名字的class文件"。

整体还是比较明了的,就是为了加载class的,并且也说了典型的做法就是去文件系统去加载同名的class文件。

在 doc 中还说了,ClassLoader 是支持并行的,后面遗弃看一下是如何支持的。

Class loaders that support concurrent loading of classes are known as parallel capable class

还有就是ClassLoader的分类,在doc中一共分为了三类:

  1. Bootstrap class loader: 启动类加载器,涉及虚拟机的,比较典型的是null
  2. Platform class loader: jdk相关的class
  3. System class loader: 也叫application class loader,platform class loader是它的祖先,一般是加载应用classpath的class
流程

整个类中最重要的就是loadClass()方法了,咱们来逐行解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  1. 方法的入参为nameresolvename刚刚在介绍里面已经说了,就是通过类名称去加载类的;那resolve标志如果为true,则找到类后需要去处理类,这里的处理doc上写的是Links the specified class.link在这里就是类加载过程中的第二阶段,链接,这一阶段就是验证、准备和解析类。

  2. synchronized (getClassLoadingLock(name))第一行是一个同步块,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    // Maps class name to the corresponding lock object when the current
    // class loader is parallel capable.
    // Note: VM also uses this field to decide if the current class loader
    // is parallel capable and the appropriate lock object for class loading.
    private final ConcurrentHashMap<String, Object> parallelLockMap;

    private ClassLoader(Void unused, String name, ClassLoader parent) {
        this.name = name;
        this.parent = parent;
        this.unnamedModule = new Module(this);
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            assertionLock = this;
        }
        this.package2certs = new ConcurrentHashMap<>();
        this.nameAndId = nameAndId(this);
    }

    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的代码里面可以看到: + getClassLoadingLock(className),判断parallelLockMap是否为null,如果不为null,则获取一个锁对象 + parallelLockMap在ClassLoader的构造器里被初始化 + ParallelLoaders有一个register方法,register(Class<? extends ClassLoader> c)用来注册可并行的ClassLoader + 如果当前的ClassLoaderParallelLoaders中注册了,则说明该类支持并行,初始化的时候创建一个ConcurrentHashMap + 回到第一点,如果parallelLockMap不为null,说明当前类加载器支持并行,加锁

总的来说,就是判断当前类加载器是否支持并行,如果支持,上锁,不支持,返回本类。

  1. Class<?> c = findLoadedClass(name);检查当前类是否已经加载过,一个native的方法去执行

  2. 下面的流程就是委派流程了,如果父类不为null,先让父类去loadClass,否则让启动类去load,如果还没找到就由本类去findClass(name),这个就是由类加载器自己去实现了。