带你走进Java世界——ClassLoader

凡事莫当前,看戏何如听戏好;
做人须顾后,上台终有下台时;

一:类加载器(ClassLoader)

JDK1.9以前,我们所熟知的JVM内置的三种类加载器:

  1. Bootstrap ClassLoader
  2. Extension ClassLoader
  3. Application ClassLoader

其中Extension ClassLoader 和 Application ClassLoader 都是URLClassLoader的子类。


1. Bootstrap ClassLoader

Bootstrap ClassLoader : 是最顶层的加载类,主要加载核心类库,主要用于加载lib/rt.jar,resources.jar,charsets.jar等模块。

我们可以通过启动指定JVM时通过-Xbootclasspath来改变Bootstrap ClassLoader的加载目录。
BootstrapClassLoader是由C代码实现的,所以我们获取不到它的对象,即每次获取都为null

2. Extension ClassLoader

Extension ClassLoader : 负责加载JVM的扩展类,加载lib/ext目录下的内容。

在JDK1.9之后,更名为:『PlatformClassLoader』

3. Application ClassLoader

Application ClassLoader : 直接面向用户的加载器,加载我们设置的classpath环境变量中的内容。我们自己所写的代码以及第三方jar包通常
都是由它来加载的。

通过ClassLoader.getSystemClassLoader() 获取到『系统类加载器』就是:AppClassLoader.我们平时所写的代码就是由它加载的。

public class ClassLoaderDemo {
    public static void main(String[] args) {
        // 1. 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:"+systemClassLoader); // ClassLoaders$AppClassLoader
        System.out.println("----------------");
        // 2. 获取用户自定义类的类加载器:默认使用系统类加载器
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        System.out.println("用户类加载器:"+classLoader); // ClassLoaders$AppClassLoader
        // 3. java内部定义的类的加载器:null (Bootstrap ClassLoader)
        System.out.println("基本类型类的加载器:" + int.class.getClassLoader()); // null 即 Bootstrap ClassLoader
    }
}

运行上面的代码,返回结果是:AppClassLoader

4. 自定义类加载器

我们可以通过继承java.lang.ClassLoader并重写其findClass(),其内部调用defineClass().

5. 这些加载器之间的关系

包含关系

首先需要强调一点,讨论『加载器』的概念,所涉及到的继承关系,并不是类之间的继承关系。确切地将,更应该称为:包含关系

5. 总结
  1. 每个Class对象都有一个ClassLoader的引用(如果classLoader == null,说明其类加载器是Bootstrap ClassLoader)
public final class Class<T> {
    private final ClassLoader classLoader;
}
  1. 每个ClassLoader都有一个parent属性(父类加载器),如果parent == null,说明它的父类加载器是Bootstrap ClassLoader
public abstract class ClassLoader {
    private final ClassLoader parent;
}

二:双亲委派机制

2.1 ClassLoader加载类的原理

当一个ClassLoader要加载某个类时(loadClass()),先把这个任务委托给它的父类加载器。即首先由最顶层的类加载器Bootstrap ClassLoader尝试去加载,
如果BootstrapClassLoader 可以加载,则任务完成,反之,则把任务转交给次一级的类加载器(Extension ClassLoader )去加载,如果还没
加载到,则转交给AppClassLoader进行加载。到这里如果还没加载到,则返回给任务的发起者,由它到指定的文件系统或者网络等URL中尝试
加载该类。此时如果还是没有加载到该类,则将会抛出ClassNotFoundException异常。如果加载到该类,则将其生成一个类的定义(defineClass()),并将其
加载到内存中,最后返回该类在内存中的Class实例对象。

这个过程可能描述起来有点绕,这里可以通过看源码java.lang.ClassLoader中的loadClass()方法来,结合其注释,来更好地理解该过程。

其实我们只需要关注ClassLoader里面的三个重要方法即可:

  1. loadClass() :定义了『双亲委派』机制
  2. findClass() : 双亲都加载不了的情况下,就会调用自定义类加载器中的findClass()来加载目标类(需要子类加载器自己实现内部逻辑)
  3. defineClass() : 组装Class对象

下面给出JVM内置的ClassLoader的简化源码:

public abstract class 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);
                    }
                } 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);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    // 空实现,需要我们自己来实现其加载逻辑
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
    
    // 将字节流转化为Class对象
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
            throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }
}

PS:一定要注意看源码的注释

2.2 双亲委派模型

再次强调一下,这里的『双亲』就是指加载系统类库的BootStrap ClassLoaderExtensionClassLoader.

2.2.1 Why使用双亲委托模型

通过前面我们的了解,JVM的加载模型是『坑爹』一族啊。但是为什么要这么设计呢?

仔细思考后不难发现,这样可以避免重复加载,因为一旦父加载器加载过该类,子加载器就不需要再动手啦。
另一方面也是出于安全方面的考虑,因为如果不采用这种设置,我们可以随心所欲的覆写自定义的JDK内部定义的类,这样做就存在很大的安全问题。
想象一下,我们自己定义一个String类型,那JDK内部的String还能被加载吗?


2.2.2 JVM判断class是否相同

JVM判断class相同,需要同时满足两个条件:

  1. 类名相同;
  2. 由同一个类加载器(ClassLoader)加载;

2.2.3 确定是『双亲』吗?

如果我们自定义类加载器,则『双亲』就可能变成『多亲』,这取决于我们的类加载器是谁,它会一直递归委派到Bootstrap ClassLoader.
如果我们没有给一个ClassLoader指定其parent,则它的parent默认是AppClassLoader.

PS:『坑爹』的属性不会变。


2.2.4 自定义类类加载器

由于JVM已经帮我们把类加载的搜索算法实现,我们只需要按照规定把类的路径给出来即可。所以自定义类加载器就变得简单起来。

  1. 继承ClassLoader;
  2. 实现findClass(),最后内部调用defineClass()即可。

三. ClassLoader在JDK 9的变化

在JDK 9, 不存在ExtensionClassLoader,取而代之的是PlatformClassLoader。另外,AppClassLoaderPlatformClassLoader
都不再继承URLClassLoader,而是JDK内部定义的类。更多详细的内容,可以参考: migration guide for Java 9


参考链接:
  1. ClassLoader的API文档
  2. 深入分析Java ClassLoader原理
  3. 老大难的 Java ClassLoader 再不理解就老了
  4. 详解Class加载过程
  5. JVM详细学习笔记
  6. ClassLoaders in java9
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值