JavaGuide知识点整理——类加载器详解
回顾一下类加载的过程
类加载过程:加载->连接->初始化。连接又可以细分三步:验证->准备->解析
一个非数组类的加载阶段(获取类的二进制字节流的动作)是可控性最强的阶段,这一阶段我们可以去自定义类加载器去控制字节流的获取方式(重写一个类的loadClass()方法)。数组类型不通过类加载器创建,它由jvm虚拟机直接创建。
所有的类都由类加载器创建,加载的作用就是将.class文件加载到内存。
类加载器总结
JVM中内置了三个重要的ClassLoader,除了BootstrapClassLoader其它类加载器均由jave实现且全部继承自java.lang.ClassLoader:
- BootstrapClassLoader(启动类加载器):最顶层的加载类,由C++实现,负责加载
%JAVA_HOME%/lib目录下的jar包和类或者被-Xbootclasspath参数指定的路径中的所有类。 - ExtensionClassLoader(扩展类加载器):主要负责加载%JRE_HOME%/lib/ext目录下的jar包和类,或被java.ext.dirs系统变量所指定的路径下的jar包。
- AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
双亲委派模型
双亲委派模型介绍
每一个类都有一个对应它的类加载器。系统中的ClassLoader在协同工作的时候会默认使用双亲委派模型。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的loadClass()处理,因此所有的请求最终都应该传送到顶层的启动类加载器BootstrapClassLoader中。当父类加载器无法处理时,才由自己来处理,当父类加载器为null时,会使用启动类加载器BootstrapClassLoader作为父类加载器。
类加载过程
每个类加载器都有一个父类加载器,我们通过下面的程序来验证。
因为这个类没有自定义类加载器,所以默认是AppClassLoader,而AppClassLoader的父类是ExtClassLoader。需要注意的是ExtClassLoader的父类的null,但是我们上面说过null不代表ExtClassLoader没有父类加载器,而是BootstrapClassLoader。
其实双亲这个翻译容易让人误解,双亲并不是父母,而是上一辈的概念。
并且类加载器中的父子关系也不是通过继承来体现的,而是由优先级来决定。
双亲委派模型实现源码分析
双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在java.lang.ClassLoader的loadClass()中,相关代码如下所示。
loadClass代码
大概流程就是首先检查请求的类是否已经被加载过。如果是直接返回,否则走下面的流程:
判断父加载器是不是空,不为空则调用父加载器。为空调用BootstrapClassLoader。
然后如果父加载器加载一场,则尝试自己加载。主要逻辑就是这样。
双亲委派模型的好处
双亲委派模型保证了java程序的稳定运行,可以避免类的重复加载(jvm区分不同类的方式不仅仅是根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类。)也保证了java的核心API不被篡改。如果没有使用双亲委派模型,而是每个类加载器都加载自己的话就会出现一些问题。比如我们编写一个称为java.lang.Object类的话,那么程序运行的时候,系统就会出现多个不同的Object类。
如果我们不想使用双亲委派模型怎么办?
自定义加载器的话,需要继承ClassLoader,如果我们不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可。无法被父类加载器加载的类最终会通过这个方法被加载。但是如果想打破双亲委派模型需要重写loadClass()方法。
自定义类加载器
除了BootstrapClassLoader其它类加载器均由java实现且全部继承自java.lang.ClassLoader.如果我们要自定义自己的类加载器,很明显也需要继承ClassLoader.
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利,生活愉快~!