JVM类加载机制

918 阅读5分钟

类的生命周期

“家宴准备了西式菜”,即家(加载)宴(验证)准备(准备)了西(解析)式(初始化)菜

18232A96-D6F2-45FE-A0F4-AA03E3785718.jpg

  • 加载(Loading),实现网络加载、热加载等,需要自定义ClassLoader时在这个阶段覆写方法
    • 通过类的全限定名获取定义此类的二进制字节流(这部分被JVM团队抽象为ClassLoader,提供给用户进行扩展)[7]
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 校验(Verification),检查字节是否格式完好(例如魔数0xCAFEBABE),确保代码不会执行非法操作
  • 准备(Preparing),为static变量分配空间、初始化零值
  • 解析(resolution) ,符号引用(如指向类或者对象域的引用)会在运行时被解析为直接引用
  • 初始化(Initialization),执行类的初始化方法(静态块代码执行,static字段赋值)
    • 加载类的时候不一定会初始化类,因此不一定会调static代码块,以下情况才会发生类的初始化,代码参考
      • new出类的实例
      • 读取类的静态字段
      • 调用类的静态方法
      • 反射调用
      • 加载子类时
      • 调用main方法
    • 以下情况不会发生类的初始化,代码参考
      • 通过子类引用父类的静态字段,只会触发父类的初始化,不会触发子类的初始化

      • 定义对象数组

      • 引用static final常量

      • 通过类名获取Class对象

      • 通过Class.forName加载类时,指定initialize=false

      • 通过ClassLoader默认的loadClass方法

为了支持Java语言的运行时绑定特性,解析阶段可能在初始化阶段之后才开始。

ClassLoader

介绍

类加载器是

  • 虚拟机团队把类加载阶段中“通过类的全限定名获取定义此类的二进制字节流”这个动作放到JVM外部去实现
  • 以便应用程序自己决定如何获取所需要的类

实现这个动作的代码模块被抽象为了“类加载器ClassLoader”。也就是说

  • JVM团队通过ClassLoader把加载阶段的部分功能外包给应用开发者自己扩展实现。[1]

ClassLoader既然暴露给开发者,那就需要有一定的基本原则,防止一些JVM的重要类加载被干扰,因此引入了双亲委派模型(Parent-Delegation Model)

双亲委派模型(Parent-Delegation Model)

双亲委派模型的结构如图。

431A7F87-BCE5-4925-80D1-83E5332F2D68.jpg

public void printClassLoaders() throws ClassNotFoundException {

    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());

    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());

    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}

执行以上代码将得到如下的输出结果,从执行结果中可以看出,不同的类所用的ClassLoader是不同的。

Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null

类加载器分为如下几种[1][2]:

  • 启动类加载器(Bootstrap ClassLoader)
    • 负责加载JDK的内部类(rt.jar等),位于$JAVA_HOME/jre/lib
    • native代码写的,负责加载其它ClassLoader,是所有ClassLoader的父加载器
  • 扩展类加载器(Extension ClassLoader)
    • 负责加载$JAVA_HOME/lib/ext或者java.ext.dirs系统变量所指的位置
  • 应用程序类加载器(Application ClassLoader)
    • 也称系统类加载器,加载-classpath或-cp目录下的类,是Extension ClassLoader的子加载器

双亲委派模型的细节可以参考 这里 ,大致意思就是将类加载的工作优先交给父加载器处理,类似于Object这样的内部核心类确保只能有一套。特点:

  • 系统类防止内存中出现多份同样的字节码
  • 防止核心类被篡改,保证Java程序安全稳定运行
  • 可见性,子加载器可以看到父加载器加载的类,但是父加载器看不到下面的(因此JDBC才需要打破Parent-Delegation Model)

双亲委派模型的实现代码参考JDK源码 ClassLoader#loadClass(String name)

ClassLoader的各个方法

  • loadClass,JVM会调用它,可以理解为ClassLoader的入口。根据全限定名去加载类,双亲委派模型的执行过程就在这个方法里。双亲处理不了的时候,当前ClassLoader会执行findClass方法
    • 重写这个方法可以打破双亲委派模式,可以支持热加载
  • findClass,根据全限定名去加载类,会调用defineClass方法
    • 重写这个方法可以从网络上加载Class字节码
  • defineClass,把字节码转换成Class,我们不需要重写,也没法重写这个方法(因为已经被定义为final)

自定义ClassLoader的使用场景

  • 资源隔离
    • tomcat,海若
  • 依赖冲突处理
    • pandora:每个中间件用自定义的加载器,中间件根据配置信息将需要暴露出的class放到一个hashmap里,给业务代码持有
  • 热部署
    • tomcat、spring boot devtools 参考
  • 代码保护
  • 不得不打破双亲委派——JDBC,详见 JDBC是如何打破双亲委派模式的

参考

1、关于JVM类加载机制,看这一篇就够了

2、Class Loaders in Java | Baeldung

3、java - How Tomcat Classloader separates different Webapps object scope in same JVM? - Stack Overflow

4、万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题….. - Java知音号 - 博客园

5、java - Why the book says JDBC damage the Parental delegation model? - Stack Overflow

6、Why do JDBC and Tomcat destroy the parental delegation model? - Katastros

7、Class loading - IBM Documentation

8、Chapter 5. Loading, Linking, and Initializing

9、 Java 类加载器(ClassLoader)的实际使用场景有哪些? - 知乎

10、class卸载、热替换和Tomcat的热部署的分析_sdmjhca的专栏-CSDN博客_jsp热替换