带你走进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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值