双亲委派模型
Last updated
Last updated
类加载器:将Java字节码加载为**java.lang.Class
实例**
特点:
动态加载。可在程序运行过程中动态按需加载字节码(字节码可以是jar、war、远程和本地.class文件)
类加载器加载类时,该类所有的依赖类都由这个类加载器一并加载。
类的唯一性由加载它的类加载器和类本身决定,只有两个类是被同一类加载器加载的前提下,比较两个类是否相等才有意义(equals
、isInstance
、instanceof
)。即使两个类源于同一Class文件,被同一JVM加载,只要加载它们的类加载器不同,这两个类就不同。
类加载器可分为两种:
启动类加载器:C++实现,虚拟机自身的一部分
继承自java.lang.ClassLoader
的类加载器,包括扩展类加载器、应用程序类加载器、自定义类加载器
启动类加载器(Bootstrap ClassLoader
):负责加载<JAVA_HOME>\lib
目录。启动类加载器无法被Java程序直接引用
扩展类加载器(Extension ClassLoader
):负责加载<JAVA_HOME>\lib\ext
目录。该类加载器由sun.misc.Launcher$ExtClassLoader
实现。扩展类加载器由启动类加载器加载,其父类加载器为启动类加载器(parent=null
)
应用程序类加载器(Application ClassLoader
):负责加载用户类路径ClassPath
上所指定的类库,由sun.misc.Launcher$App-ClassLoader
实现。开发者可直接通过java.lang.ClassLoader
中的getSystemClassLoader()
方法获取应用程序类加载器,所以也可称它为系统类加载器。应用程序类加载器也是启动类加载器加载的,但是它的父类加载器是扩展类加载器。在一个应用程序中,系统类加载器一般是默认类加载器。
BootStrap ClassLoader
由于启动类加载器(Bootstrap ClassLoader)是由C++实现的,而C++实现的类加载器在Java中是没有与之对应的类的,所以拿到的结果是null。
Extension ClassLoader
Application ClassLoader
JVM并非在启动时就把加载了所有.class
文件,而是在程序运行过程中动态按需加载。除了启动类加载器外,其他所有类加载器都需要继承抽象类ClassLoader
每个类加载器都持有一个 parent
字段,指向父加载器。(AppClassLoader
的parent
是ExtClassLoader
,ExtClassLoader
的parent
是BootstrapClassLoader
,但ExtClassLoader
的parent=null
。)
loadClass
方法实现了双亲委派机制:先检查这个类是否被加载过,若未加载过委派给父加载器加载,递归调用,一层层向上委派,若到最顶层的类加载器(BootStrap ClassLoader
)无法加载该类,再一层层向下委派给子类加载器加载
findClass
:找到字节码文件并读入得到字节码数组,传入defineClass
实现类加载
defineClass
:将Java类的字节码解析为java.lang.Class
实例
优点:
安全:防止用户伪造的恶意类覆盖原系统类。例如类java.lang.Object
,它存放在rt.jar
之中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器加载
避免多个类加载器将同一类重复加载
缺点:
典型问题:加载 SPI 实现类的场景
比如 JNDI服务,它的代码由启动类加载器去加载(在 JDK 1.3 时放进 rt.jar),但 JNDI 的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现的部署在应用程序的 classpath 下的 JNDI 接口提供者(SPI)的代码,上面说到类加载器加载类时,该类所有的依赖类都由这个类加载器一并加载。启动类加载器不可能去加载ClassPath下的类。此时就只能打破双亲委派机制。
由上面知道实现双亲委派机制主要是loadClass
方法,若想打破双亲委派机制,自定义类加载器继承ClassLoader
,重写loadClass
方法即可
java中所有类都继承了Object
,而加载自定义类显然还会加载其父类,而最顶级的父类Object
是java官方的类,只能由BootstrapClassLoader
加载,java禁止用户用自定义的类加载器加载java.
开头的官方类。所以就算破坏双亲委派机制,BootStrap ClassLoader
还是绕不过的,但可以跳过AppClassLoader
和ExtClassLoader
另一种破坏双亲委派机制的方法是利用线程上下文类加载器,其实是一种类加载器传递机制。可以通过java.lang.Thread#setContextClassLoader
方法给一个线程设置上下文类加载器,在该线程后续执行过程中就能把这个类加载器取(java.lang.Thread#getContextClassLoader
)出来使用。
如果创建线程时未设置上下文类加载器,将会从父线程(parent = currentThread()
)中获取,如果在应用程序的全局范围内都没有设置过,就默认是应用程序类加载器。