一、聊聊ClassLoader的那些事儿
我们要分析清楚Tomcat中的类加载器相关的内容之前我们还是需要把JVM中的类加
载器给大家理清楚。
1.类加载器的过程
类加载器的作用就是从文件系统或者网络中加载Class文件,至于他是否可以运行就不是ClassLoader的工作了。
2.类加载器的分类
JVM中支持的类加载器有两种类型,分别是 引导类加载器
【Bootstrap ClassLoader】和 自定义类加载器
【User-Defined ClassLoader】
在Java虚拟机层面定义:所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
可以通过源码看到对应的类加载器的继承关系
ExtClassLoader
AppClassLoader
通过具体的案例代码可以来看看类加载器的使用
public static void main(String[] args) { // 获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("systemClassLoader = " + systemClassLoader); // 获取父类加载器 ClassLoader parent = systemClassLoader.getParent(); System.out.println("parent = " + parent); // 继续获取上层的类加载器 ClassLoader bootstrapClassLoader = parent.getParent(); System.out.println("bootstrapClassLoader = " + bootstrapClassLoader); // 自定义Java类 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println("classLoader = " + classLoader); // Java系统类 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println("classLoader1 = " + classLoader1); } 复制代码
对应的输出结果
systemClassLoader = sun.misc.Launcher$AppClassLoader@18b4aac2 parent = sun.misc.Launcher$ExtClassLoader@61bbe9ba bootstrapClassLoader = null classLoader = sun.misc.Launcher$AppClassLoader@18b4aac2 classLoader1 = null 复制代码
3.Bootstrap ClassLoader
虚拟机自带的类加载器,启动类加载器
- 通过C/C++实现的,JVM内置类加载器
- 作用是用来加载Java的核心库(JAVA_HOME/jre/lib.rt.jar、resources.jar或者sun.boot.class.path路径下的内容)、用于提供JVM自身需要的类。
- 没有继承java.lang.ClassLoader、没有父加载器,自己就是祖先了。
- 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
- 出于安全考虑,Bootstrap启动类加载器只加载报名为java,javax,sun等开头的类
通过代码来看看具体的加载路径有哪些
public static void main(String[] args) { System.out.println("**************启动类加载器*************"); URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0 ; i < urLs.length ; i ++){ URL urL = urLs[i]; System.out.println("urL = " + urL.toExternalForm()); } } 复制代码
输出结果:
**************启动类加载器************* urL = file:/D:/software/java/jdk8/jre/lib/resources.jar urL = file:/D:/software/java/jdk8/jre/lib/rt.jar urL = file:/D:/software/java/jdk8/jre/lib/sunrsasign.jar urL = file:/D:/software/java/jdk8/jre/lib/jsse.jar urL = file:/D:/software/java/jdk8/jre/lib/jce.jar urL = file:/D:/software/java/jdk8/jre/lib/charsets.jar urL = file:/D:/software/java/jdk8/jre/lib/jfr.jar urL = file:/D:/software/java/jdk8/jre/classes 复制代码
4.Extension ClassLoader
虚拟机自带的加载器。扩展类加载器,Java语音编写,是sun.misc.Launcher的内部类
派生于ClassLoader所以是自定义类加载器
父类加载器是BootstrapClassLoader。
扩展类加载器是从扩展目录 java.ext.dirs
系统属性指定的目录中加载类库,或者从JDK的安装目录的 jre/lib/ext
子目录下加载雷凯,如果用户创建的jar包也放在了这个目录下,那么该类加载器也会自动加载的。
然后通过案例来看看扩展类加载器加载的路径
public static void main(String[] args) { System.out.println("**************扩展类加载器*************"); String extDirs = System.getProperty("java.ext.dirs"); System.out.println("extDirs = " + extDirs); } 复制代码
对应的输出结果
**************扩展类加载器************* extDirs = D:\software\java\jdk8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext 复制代码
然后通过加载路径下的Java类测试也能证明
5.App ClassLoader
虚拟机自带的加载器,APPClassLoader也叫应用程序类加载器。Java语音编写,由sun.misc.Launcher下的内部类提供。也是派生于ClassLoader。
父类加载器为ExtClassLoader
主要负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库,该类加载器是程序中的默认类加载器,一般来说,Java应用的类都是由它来完成加载的。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。
6.自定义类加载器
在我们的日常应用程序的开发中,我基本是用不到自定义类加载器的,基本就是由前面介绍的这三个类加载器来相互配合搞定的。但是在有些特殊的情况下我们不希望通过系统的类加载器来处理,这时我们就可以通过自定义类加载来实现了。
用户自定义类加载器的实现步骤:
- 我们可以直接编写 java.lang.ClassLoader 类的实现来完成自定义的处理
- 在JDK1.2之前,自定义类加载器时我们总是会继承ClassLoader然后重写loadClass方法,达到自定义类加载的目的,但是在JDK1.2之后建议把自定义的类加载逻辑放在findClass()方法中
- 最后在编写自定义类加载的时候,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样可以达到简化自定义类加载器的目的
7.双亲委派机制
Java虚拟机对class文件采用的是
按需加载
的方式,也就是当需要使用该类时才会将他的class文件加载到内存中生成class对象,而且加载某个类的class文件时,Java虚拟机采用的而是双亲委派模式。即把请求交由父类加载器处理,它是一种任务委派模式。
双亲委派的工作原理:
- 如果一个类加载器收到了类加载的请求,它并不会自己先去加载,而且把这个请求委托给父类的加载器去执行。
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求,最终将请求流转到顶层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回,如果父类无法完成任务,子加载器才会尝试自己去加载。
举个简单的例子,我们自定义一个java.lang.String类,然后添加对应的main方式执行。
public class String { public static void main(String[] args) { System.out.println("自定义String类"); } } 复制代码
报错信息为:
原因就是双亲委派机制通过BootstrapClassLoader加载的是java包下的String,而不会加载我们自定义的。
双亲委派机制的有点:
- 避免类的重复加载
- 保护程序的安全,防止核心API被随意的篡改