JVM的类加载过程与双亲委派机制

141 阅读5分钟

1. 类加载过程

java文件首先生成jar包,首先需要通过类加载器把主类加载到JVM中。

注意:jar包中的类是逐步加载,即使用到时才加载。

整个过程归纳如下:

加载-验证-准备-解析-初始化-使用-卸载 下面依次分析每步骤的意义:

  • 加载:类是存放在磁盘上的,首先在硬盘上查找并通过IO读入字节码文件,把该类加载到内存区,具体使用到某类时才会加载。例如使用main()方法。
  • 验证:校验字节码的正确性
  • 准备:给类的静态变量赋初始值(比如:整形赋值为0)
  • 解析:将符号引用替换为直接引用。(变量/方法称为符号,它们在内存中的实际地址就是代码的直接引用)
  • 初始化:对类的静态变量初始化为指定的值

image.png

2.类加载器和双亲委派机制

2.1类加载器

类加载过程主要是通过类加载器来实现的,主要包括如下几种类加载器:

  • 启动类加载器:负责加载位于JRE的lib目录下的最核心类库,比如rt.jar
  • 扩展类加载器:负责加载位于JRE的lib目录下的扩展目录中的JAR类包
  • 应用程序类加载器:负责加载自己写的类
  • 自定义类加载器:负责加载指定路径下的类包

以下是一个类加载器示例

public class TestJDK {
    public static void main(String[] args){
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDK.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
        }
    }

运行结果如下:

sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader

Process finished with exit code 0

2.2双亲委派机制

d051ada3baae70ebadd75d6ea8c9456.png

加载某个类时先委托父加载器寻找目标类,找不到就再委托上层父加载器进行加载,当所有的父加载器的加载路径下都找不到目标类时,该类就在自己的类加载路径中查找并加载目标类。

简单总结一下,就是先让爹加载,不行再由儿子自己加载。

e.g:加载main()类。加载顺序是应用程序加载器>扩展类加载器>启动类加载器,并且都没在自己的类加载路径里找到main()类,此时,则向下退回加载math类的请求,扩展类加载器收到回复就在自己的类加载路径中找math()类,没找到又向下退回math()类的加载请求给应用程序类加载器,应用程序类加载器在自己的类加载路径中找math()类,最终找到了就可以自己加载了。

2.3为什么要设计双亲委派机制

  • 沙箱安全机制:自己写的核心类不会被加载,防止核心类库被修改
  • 避免类的重复加载:当爹已经加载了该类时,就没必要再让ClassLoader再加载了,保证类只加载一次

2.4打破双亲委派机制

以Tomcat类加载机制为例,Tomcat是一个web容器,它要解决如下问题:

  1. 同一类库的不同版本。一个web容器可能需要部署多个应用程序,不同应用程序可能会依赖相同类库的不同版本,因此需要保证每个应用程序的类库独立,是相互隔离的;
  2. 同类共享。部署再同一个web容器中的相同类库的相同版本可以共享,否则,会重复加载相同类库到虚拟机;
  3. 避免web容器类与应用程序类库混淆。web容器也有自己依赖的类库,不能与应用程序的类库混淆,因此需要让容器的类库和程序的类库隔离开来;
  4. web容器需要支持jsp修改,jsp文件也需要编译成class文件才能加载到虚拟机中,但程序运行后修改jsp已经是司空见惯的事情,web容器需要支持程序运行后修改jsp文件而不用重启。

而如果Tomcat使用默认的双亲委派机制会出现以下问题:

  1. 问题1和3的解释:无法加载相同类库的不同版本。默认加载机制只根据你的类名加载,不管你要用的是什么版本,是独有的;
  2. 问题2的解释:使用默认的双亲委派机制能够实现,因为它的职责就是保证类加载的唯一性;
  3. 问题4的解释:jsp文件其实就是class文件,如果我们修改了文件内容但是类名还是原来的类名,类加载器还是会直接在加载路径中加载方法区中已经存在的类名,所以修改后的jsp文件是不能重复加载的。咋解决呢?当一个jsp文件被修改后,就把加载这个类的类加载器卸载,然后重新创建类加载器,重新加载jsp文件。

Tomcat自定义类加载器

a3aa9b63bafd15be0849ebfb851901a.png 从委派关系图可以看出:

  • CommonClassLoader能加载的类可以被CatalinaClassLoader和SharedClassLoader使用,从而实现公有类库共享,而CatalinaClassLoader和SharedClassLoader自己加载的类相互隔离开来;
  • WebAppClassLoader则可以使用父加载器即SharedClassLoader加载到的类,但各个WebAppClassLoader相互隔离开来,避免混淆;
  • 最下层的Jsp类加载器加载的仅是JSP文件编译的Class文件,当Jsp文件被修改后会用新的Jsp类加载器替换到当年前的Jsp类加载器,实现Jsp文件的热加载功能。 所以Tomcat类加载器打破了双亲委派机制,实现了应用程序间的隔离