文章目录
- Java类加载器原理和实践操作
- 1、Java程序的启动并运行的过程
- 2、类加载器加载过程
- 1、Java8中内置的类加载器
- 1、三种类加载器
- 2、三种加载器之间的关系
- 3、类加载的双亲委派机制
- 1、双亲委派模型
- 2、ClassLoader
- 3、类加载器的特性
Java类加载器原理和实践操作
注:本文章中提到的“类”,是“非数组类 ”和“接口”的统称。
1、Java程序的启动并运行的过程
Java中的类加载都是在运行时动态完成的,这种动态加载的特性就是java语言灵活性的根源。同时在Java中,所有的类加载都是通过类加载器(ClassLoader)来完成。
2、类加载器加载过程
- 首先使用java代码或者jvm触发一个加载动作。
- 将类的全限定名传给类加载器。
- 类加载器通过类名获取到字节码的二进制流
- 根据字节码二进制流创建加载对应的Class对象
流程图图解:
1、Java8中内置的类加载器
运行示例及运行结果如图所示:
- 由上我们可以发现Java8中内置的三个ClassLoader,分别是APPClassLoader、ExtClassLoader和BootstrapClassLoader。他们就是最主要的三种类加载器。
1、三种类加载器
- APPClassLoader,应用类加载器,默认的系统类加载器(System ClassLoader)
- 应用类加载器用来加载当前Java应用classpath中的类
- ExtClassLoader,拓展类加载器
- 拓展类加载器负责加载拓展目录中的类
- BootStrap ClassLoader,启动类加载器
2、三种加载器之间的关系
三种类加载器之间的关系如下图所示:
有两点需要注意:
- 从JVM的角度来看,只有两种类加载器。一种是BootStrap ClassLoader,他通常是JVM中的一部分,使用C/C++语言原生实现。另一种是用户定义的ClassLoader,包括了JDK中内置的APPClassLoader和ExtClassLoader以及用户自行实现的ClassLoader,其中用户定义的ClassLoader都是使用Java语言来实现的,并且要求他们都继承抽象类
java.lang.ClassLoader
- BootStrap ClassLoader、APPClassLoader和ExtClassLoader并非是继承的关系,而是组合的关系。APPClassLoader显式拥有一个parent加载器ExtClassLoader,而ExtClassLoader加载器则隐式指向BootStrap ClassLoader。
3、类加载的双亲委派机制
双亲委派模型,英文名是“Parents Delegation Model”。
- 翻译的特别差,容易产生一些误解:
- 误解1:“双亲”,会被误解为存在两个“父/母”类加载器(这是错误的)
- 1的正确理解:除了BootStrap ClassLoader,其他的类加载器有且只有一个parent
- 误解2:“parent”,容易被误解为继承关系中的父类(这是错误的)
- 2的正确理解:并非继承关系,而是组合关系
1、双亲委派模型
- 每个Class都有对应的ClassLoader
- 每个ClassLoader都有一个“父”类加载器(parent ClassLoader)。BootStrap ClassLoader除外,他是最顶层的类加载器。
- 对于一个类加载的请求,总是优先委托给“父”类加载器来尝试加载
- 对于用户自定义的类加载器,默认的“父”类加载器是AppClassLoader
类加载委派流程如下图所示:
所以当系统加载String类时,即使你自己定义了一个同名的String类,并且将其放置到classpath中,但是最终还是会首先委派给处于最顶层的启动类加载器中加载,即最终会加载rt.jar中的String类,而不是classpath中自行定义的String类。这样可以保证java运行的稳定性。
加载String类流程如图所示:
双亲委派机制的实现代码非常简单,可以参考java.lang.ClassLoader
中的方法LoadClass,核心代码十行左右。
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//1 首先检查类是否被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//2 没有则调用父类加载器的loadClass()方法;
c = parent.loadClass(name, false);
} else {
//3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//4 若父类加载失败,抛出ClassNotFoundException 异常后
c = findClass(name);
}
}
if (resolve) {
//5 再调用自己的findClass() 方法。
resolveClass(c);
}
return c;
}
2、ClassLoader
关键类ClassLoader中的几个关键方法:
- LoadClass(…)
此方法根据类的权限定名来加载并创建一个类对象的入口,如果遵循“双亲委派机制”,那么ClassLoader的子类尽量不要覆盖此方法。不过如果 需要打破双亲委派模型,那么ClassLoader的子类需要覆盖此方法,换成自行定义的逻辑。
- defineClass(…)
此方法将字节码的字节流转换成为一个Class对象,这是一个final方法,意味着子类无法覆盖它,最终是通过一个native原生方法,将字节流转换成Class对象。
- findClass(…)
此方法根据类的全限定名,获取字节码二进制流,并创建对应的Class对象,如果遵循“双亲委派模型”,那么我们通常不会覆盖LoadClass(…)方法,而是覆盖findClass(…)方法。
此方法的实现逻辑:a. 首先根据参数name,从指定来源获取字节码的二进制流。b. 然后调用defineClass(…)方法,创建一个class对象。
- findBootstrapClassOrNull(…)
此方法根据类的全限定名,委派BootStrap ClassLoader进行类的加载,注意这个方法是private的,这意味着:如果要将某个类加载的请求委派给BootStrap ClassLoader,那么必须间接调用类ClassLoader中的某个public和protected的方法。
- getParent()
此方法获取当前ClassLoader的“父”类加载器。
- 注意parent字段是private final的,他只能在构造函数中初始化。
- parent!=null时,调用parent.loadClass(…)将加载请求委派给parent。
- parent==null时,调用findBootstrapClassOrNull(…)将请求委派给BootStrap ClassLoader。
3、类加载器的特性
- 用来确定类的“唯一性”
- N:某个类的全限定名
- L:加载定义这个类的类加载器ClassLoader
- N,L:二元组<N,L>,可以用来确定类的唯一性
所以在查看两个类的相等,除了查看两个类的全限定名之外,还需要查看加载这两个类的类加载对象是否是同一个。
示例如下图所示:用两个类加载器加载同一个类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mBvQJnz-1662601557000)(https://gitee.com/acrazydragon/note_collection/raw/master/img/image-20220904221120569.png)]
示例运行结果如下图所示:
- 传递性
- 假设类C是由加载器L1定义加载的,那么类C中所依赖的其他类也会通过L1来进行加载。
类加载的实例流程图如下图所示:
示例运行结果如下图所示:
[外链图片转存中…(img-QGVDvMsz-1662601557000)]
- 传递性
- 假设类C是由加载器L1定义加载的,那么类C中所依赖的其他类也会通过L1来进行加载。
类加载的实例流程图如下图所示:
[外链图片转存中…(img-ehA47YrN-1662601557001)]
- 可见性