类加载过程
在了解什么是双亲委派之前,需要先了解类的加载过程:
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
- 加载: 在磁盘上查找并通过io读入字节码文件,一般是使用类的时候才会加载。 在加载阶段内存中会生成一个代表该类的类class对象。是程序访问的入口。
- 验证: 验证字节码文件的合理性和正确性
- 准备: 给类的静态变量分配内存,并赋予初始值,该值与代码无关。 比如boolean类型的默认值是false;
- 解析: 将符号引用替换成直接引用,程序加载的时候将一些静态变量,全局变量的引用提前加载好。
- 初始化: 给静态变量初始化制定的值,执行静态代码块的方法。
类的加载是有类加载器来实现的,Java里有如下几种类加载器
- 引导类加载器(bootstrapLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
- 扩展类加载器(extClassloader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
- 应用程序类加载器(appClassLoader):负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
- 自定义加载器:负责加载用户自定义路径下的类包
类加载器实现源码
在类的加载时会创建JVM启动器实例sun.misc.Launcher。 sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是
- sun.misc.Launcher.ExtClassLoader(扩展类加载器);
- sun.misc.Launcher.AppClassLoader(应用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序 其中Launcher类的getClassLoader方法中获取的是AppClassLoader类的实现类,详细请看下面的源码
双亲委派
在了解了类的加载过程以及类加载器后,下面来介绍双亲委派的流程
- jvm首先调用系统类加载器的loadClass方法(String name)来获得类的class对象;最先找到的是应用程序类加载器。
- 应用程序类加载器委托给拓展类加载器,拓展类加载器委托给引导类加载器,引导类加载器是jvm的根加载器,其没有委托对象,尝试自己加载类的class对象,在引导类加载器自己的类加载路径下查找该类,如果找到直接返回,没有的话再委托给扩展类加载器。
- 扩展类加载器会在自己的类加载路进行进行寻找该类的对象,找到返回没有找到再向下委托给应用程序类加载器。
- 应用程序类加载器在自己的类加载路径下(classpath下)查找该类,找到后加载返回给jvm
简单总结双亲委派就是在类加载过程中,类加载器会首选让其父加载器来优先加载该类,只有当父类无法加载时才会向下委托其子类加载器来加载类的过程。
核心源码摘要:
- ClassLoader.loadClass方法,来获取类信息。
- 会先使用findLoadedClass方法来检查当前类加载器是否已经加载了该类,刚开始的时候是获取不到的。
- 如果为null,会让父类去执行loadClass方法,扩展类和引导类加载器在项目启动时已经将自己加载路径下的类加载完成,此时通过findLoadedClass方法查找该类是否被加载过了。
- 如果当parent == null,意味着此时是扩展类加载器了,这个时候会调用findBootstrapClassOrNull,通过引导类加载器来加载。
- 如果双亲都找不到,此时会调用findClass方法,由AppClassLoader来加载。此时会调用URLClassLoader的findClass方法来加载该类。
接着我们来看URLClassLoader的findClass方法方案源码:
通过获取类的class文件来由AppClassLoader来加载类。
双亲委派的用途
为什么类的加载要用双亲委派这种方式来实现呢? 其主要原因如下:
- 沙箱安全机制: java的核心类库不会被恶意篡改。就算写了相同的类路径 但是也没有办法加载到个人内容。
- 避免类的重复加载: 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
打破双亲委派
在了解了双亲委派的过程和用途后,不禁思考,这种方式是不是就是万能有效的呢? 有没有场景不适合这种模式?
答案显然是有的,这就引出了大家都知道的Tomcat打破双亲委派。 那为什么Tomcat就要去打破这种机制呢?
原因我理解有以下几点:
-
由于其是一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
-
部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
-
web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。