引题
在Java程序启动时,类信息会由类加载器加载到jvm,这些类加载器也是Java类,也需要被加载,我们都知道Java虚拟机是C写的,Java.exe会调用底层的jvm.dll(可以理解为C的程序包,类似于Java的jar包) 创建sun.misc.Launcher 实例,进而来创建Java的类加载器。
类加载器
Java的类加载器有如下几种:
- 引导类加载器(bootstrapLoader):负责加载支撑jvm运行的核心类库,位于JRE lib目录下(rt.jar、charset.jar)。
- 扩展类加载器(sun.misc.Launcher$ExtClassLoader):负责加载支撑jvm运行的扩展类库,位于JRE lib目录下 ext目录下的jar包。
- 应用程序类加载器(sun.misc.Launcher$AppClassLoader):负责加载classpath路径下的包,也就是自己程序的类。
- 自定义类加载器:负责加载用户自定义路径下的类。
从运行结果来看:
String类的加载器是null,因为引导类加载器是C的,所以我们在Java程序中获取不到。
com.sun.crypto.provider.DESKeyFactory类由扩展类加载器加载。
自己写的类由应用程序类加载器加载。
类的继承关系如下图:
最终都是继承java.lang.ClassLoader类
类加载器初始化过程
上面提到Java的类加载器是由sun.misc.Launcher类完成初始化的。
31 行:静态变量launcher。当 Launcher 类被C程序创建时,会调用 new Launcher()。
43 行:创建扩展类加载器。
49 行:创建应用程序类加载器。
初始化ExtClassLoader
271 行:无非也是 new 了个对象。
284 行:调用到父类构造创建父类对象。并且传了一个空值最终赋值给了 parent 属性,说明 扩展类的加载器为空,这也验证了上述 extClassloader.getParent()为空,在Java程序获取不到引导类加载器。
初始化AppClassLoader
49 行:创建应用程序类加载器,并且将刚刚创建的扩展类加载器作为入参传入,最终赋值给parent属性。最终创建的应用程序类加载器赋值赋值给了Launcher类的ClassLoader属性。
总结:
扩展类加载器、应用程序类加载器都是由 Launcher 类创建的。
应用程序类加载器 的父加载器是 扩展类加载器。
扩展类加载器 的父加载器是 引导类加载器。
自定义类加载器
类加载器通过 loadClass()中的 findClass()加载类信息,而上面说到类加载器的父类是 ClassLoader 类,所以需要继承该类,并且重写findClass()。
至于findClass逻辑为何这样写,且往下看。
双亲委派机制
我们已经知道了类加载器各自负责的加载类路径以及各自的父加载器,在一个类被加载时,并不是直接由对应的类加载器去加载,而是先委托父加载器加载,当父加载器无法加载时再让子加载器加载。
源码分析
当执行 Launcher.getClassLoader.loadClass()加载类时,首先会获取到AppClassLoader 然后调用 ClassLoader 类 loadClass(),遵循双亲委派机制。
409 - 414 行:如果父加载器存在就调用父加载器的loadClass(),如果没有则调用引导类加载器加载。
424 行:如果父加载器没有加载该类,则自己加载,调用findClass()
这是AppClassLoader ExtClassLoader 加载类的逻辑,最终会调用到defineClass()
而defineClass()是ClassLoader类定义的方法,URLClassLoader类进行了重写。
再回过头看上面的自定义类加载器,也是重写findClass()方法最终调用到defineClass()
为什么要设计双亲委派机制 ???
- 沙箱安全机制,防止Java核心类被更改。
- 避免类的重复加载。
为什么双亲委派机制要从AppClassLoader开始 ???
- 提升性能,因为实际情况下,一个Java项目大部分都是我们自己写的类。
打破双亲委派机制
双亲委派机制就是loadClass(),只需要重新该方法即可。
Tomcat打破双亲委派机制
tomcat为什么要打破?
一个tomcat可能同时运行了两个项目,这两个项目用的Spring版本不一样(存在许多相同类名的类),当项目B运行需要加载某个类(1.0版本)时,发现该类已经由项目A加载过了(1.2版本),那就直接用,最终导致类信息不匹配程序报错。
tomcat的几个主要类加载器:
- commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的
全盘负责委托机制
全盘负责是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
总结
Java的类加载器有如下几种,并且加载类时遵循双亲委派机制。
- 引导类加载器(bootstrapLoader):负责加载支撑jvm运行的核心类库,位于JRE lib目录下(rt.jar、charset.jar)。
- 扩展类加载器(sun.misc.Launcher$ExtClassLoader):负责加载支撑jvm运行的扩展类库,位于JRE lib目录下 ext目录下的jar包。
- 应用程序类加载器(sun.misc.Launcher$AppClassLoader):负责加载classpath路径下的包,也就是自己程序的类。
- 自定义类加载器:负责加载用户自定义路径下的类。
注:本文通过源码 + 行说明的方式进行描述,若不好理解可留言。本文仅为个人学习记录,有错误的地方,请大佬们指正。