JVM设计者把
类加载阶段中
的“通过'类全名'来获取定义此类的二进制字节流”
这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”
。
类与类加载器
对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。
双亲委派模型?
oracle 官方文档关于 jvm 类加载机制所用的描述是:
The Java platform uses a delegation model for loading classes. The basic idea is that
every class loader has a "parent" class loader
. When loading a class, a class loader first “delegates” thesearch for the class to its parent class loader before attempting to find the class itself
.
翻译过来就是:
java 平台使用 委派模型来加载类。 基本思想就是,
每一个类加载器都有一个父加载器
, 当需要加载一个 class时,首先把该 class 的查询和加载优先委派给父加载器进行
, 如果父加载器无法加载该 class, 再去尝试自行加载这个 class
JDK 默认提供了如下几种ClassLoader?
- Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,
它主要负责加载%JAVA_HOME%/jre/lib
,-Xbootclasspath参数指定的路径
以及%JAVA_HOME%/jre/classes
中的类rt.jar
- ExtClassLoader
Bootstrploader加载ExtClassLoader,并且
将ExtClassLoader的父加载器设置为Bootstrploader
,
ExtClassLoader是用Java写的,具体来说就是sun.misc.Launcher$ExtClassLoader
,
ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext
,此路径下的所有classes目录
以及java.ext.dirs系统变量指定的路径中类库。
- AppClassLoader
Bootstrploader加载完ExtClassLoader后,就会加载AppClassLoader,并且
将AppClassLoader的父加载器指定为ExtClassLoader
。AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader
,
另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
双亲委派机制的工作流程?
1当前ClassLoader
首先从自己已经加载的类中查询是否此类已经加载
,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存
,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2当前classLoader的
缓存中没有找到被加载的类的时候
,委托父类加载器去加载
,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader
.
3.
当所有的父类加载器都没有加载的时候
,再由当前的类加载器加载,并将其放入它自己的缓存中
,以便下次有加载请求的时候直接返回。
从上图中我们就更容易理解了,当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。那么有人就有下面这种疑问了?
为什么要设计这种机制?优势是什么?
这种设计的好处是,
避免重复加载 + 避免核心类篡改
。
避免重复加载
:Java类随着它的类加载器一起具备了一种带有优先级的层次关系
,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
避免核心类篡改
:如果有人想替换系统级别的类:String.java。篡改它的实现
,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader
),所以其他类加载器并没有机会再去加载
,从一定程度上防止了危险代码的植入。
这个写的系统且详细:https://blog.csdn.net/lengxiao1993/article/details/86689331
参考1:https://blog.csdn.net/lengxiao1993/article/details/86689331
参考2:https://blog.csdn.net/u010312474/article/details/91046318
参考3
参考4
说说为啥要打破双亲委派模型,如何打破?
JDBC
之所以要破坏双亲委派模式
是因为,JDBC的核心在rt.jar中由启动类加载器加载,而其实现则在各厂商实现的的jar包中,根据类加载机制,若A类调用B类,则B类由A类的加载器加载,也就是说启动类加载器要加载jar包下的类,我们都知道这是不可能的,启动类加载器负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,
那么JDBC是如何加载这些Driver实现类的?
通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类
。
还有就是我们可以自定义的加载器继承ClassLoad然后修改的loadClass和find classde 方法,从而可以打破双亲的委派机制
原文链接:https://blog.csdn.net/qq_42820805/article/details/113750352