类加载
Java是运行在Java的虚拟机(JVM)中的,在初步学习Java时,我们都知道,编写的Java源代码会被编译器编译成.class的字节码文件。然后ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
双亲委派机制
通俗点讲,双亲可以理解为父类,当.class文件需要被jvm进行加载的时候,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法加载的时候(比如范围超过可搜索区域),当前加载器才会执行父类的加载;
需要注意的是,自定义加载器在“优先级”上,是最低的,所以简单来讲,自定义的classLoader去加载自己的类,一定是父级们都没有办法加载的情况下。
为什么要使用双亲委派机制
1.为了防止核心类被篡改
用户自己编写了一个称为java.lang.Object的类,在没有双亲委派模型的情况下,将会用自定义的类加载器加载,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
2.防止类的重复加载
双亲委派模型的弊端
双亲委派模型会带来一个问题,即上层的ClassLoader无法访问下层的ClassLoader所加载的类;
哪些情况下需要改变双亲委派模型
1.第一次破坏
由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。
在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,这是因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。
用户重写了loadClass才能实现自己的类加载逻辑。
2.第二次破坏
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题:越基础的类由越上层的加载器进行加载。基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。
如果基础类又要调用回用户的代码,那该么办?
一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
注:在java平台中,通常把核心类(rt.jar)中提供外部服务、可由应用层自行实现的接口称为Service Provider Interface,即SPI;
3.第三次破坏
热部署、无感升级等新功能,不需要程序重启即可将.class文件加载进jvm,这个过程中会破坏双亲委派模型
参考链接:https://blog.csdn.net/weixin_43889841/article/details/112170013