目录

JVM类加载器

类加载器

启动类加载器(Bootstrap ClassLoader)

负责加载 JAVA_HOME\lib 目录的或通过-Xbootclasspath参数指定路径中的且被虚拟机认可(rt.jar)的类库

扩展类加载器(Extension ClassLoader)

负责加载 JAVA_HOME\lib\ext 目录或通过java.ext.dirs系统变量指定路径中的类库

应用程序类加载器(Application ClassLoader)

负责加载用户路径classpath上的类库

自定义类加载器(User ClassLoader)

加载应用之外的类文件

/images/jvm/jvm-cl.png
JVM类加载器

执行顺序

  1. 检查顺序是自底向上:加载过程中会先检查类是否被已加载,从Custom到BootStrap逐层检查,只要某个类加载器已加载就视为此类已加载,保证此类所有ClassLoader只加载一次
  2. 加载的顺序是自顶向下:也就是由上层来逐层尝试加载此类。

加载时机与过程

类加载的四时机:

  1. 遇到new、getStatic、putStatic、invokeStatic四条指令时
  2. 使用java.lang.reflect包方法时,对类进行反射调用
  3. 初始化一个类时,发现其父类还没初始化,要先初始化其父类
  4. 当虚拟机启动时,用户需要指定一个主类main,需要先将主类加载

一个类的一生

/images/jvm/jvm-class-left.png
类的生命周期

类加载做了什么?主要做三件事

  1. 类全限定名称 → 二进制字节流加载class文件
  2. 字节流静态数据 → 方法区(永久代,元空间)
  3. 创建字节码Class对象

JVM类加载机制

  • 全盘负责, 当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
  • 双亲委派机制, 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

类加载途径

  1. jar/war
  2. jsp生成的class
  3. 数据库中的二进制字节流
  4. 网络中的二进制字节流
  5. 动态代理生成的二进制字节流
    /images/jvmjvm-cl-channel.png
    类加载途径

双亲委派模型与打破双亲委派

什么是双亲委派?

  • 当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务

为什么需要双亲委派呢?

  • 主要考虑安全因素,双亲委派可以避免重复加载核心的类,当父类加载器已经加载了该类时,子类加载 器不会再去加载
  • 比如:要加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载,最终都委托给顶层的启动 类加载器进行加载,这样就可以保证使用不同的类加载器最终得到的都是同样的Object对象。

为什么还需要破坏双亲委派

  • 在实际应用中,双亲委派解决了Java 基础类统一加载的问题,但是却存在着缺陷。JDK中的基础类作为典型的API被用户调用,但是也存在API调用用户代码的情况,典型的如:SPI代码。这种情况就需要打破双亲委派模式。
  • 数据库驱动DriverManager。以Driver接口为例,Driver接口定义在JDK中,其**实现由各个数据库的服务商来提供,由系统类加载器加载。**这个时候就需要 启动类加载器来委托 子类来加载Driver实现,这就破坏了双亲委派。

如何破坏双亲委派

重写ClassLoader的loadClass方法

  • 在 jdk 1.2 之前,那时候还没有双亲委派模型,不过已经有了 ClassLoader 这个抽象类,所以已经有人继承这个抽象类,重写 loadClass 方法来实现用户自定义类加载器。
  • 而在 1.2 的时候要引入双亲委派模型,为了向前兼容, loadClass 这个方法还得保留着使之得以重写,新搞了个 findClass 方法让用户去重写,并呼吁大家不要重写 loadClass 只要重写 findClass。
  • 这就是第一次对双亲委派模型的破坏,因为双亲委派的逻辑在 loadClass 上,但是又允许重写loadClass,重写了之后就可以破坏委派逻辑了。

双亲委派机制是一种自上而下的加载需求,越往上类越基础,SPI代码打破了双亲委派

  • 以数据库驱动DriverManager为例,线程上下文类加载器(ThreadContextClassLoader)

热部署和不停机更新用到的OSGI技术

  • 为了满足热部署、不停机更新需求。OSGI 就是利用自定义的类加载器机制来完成模块化热部署,而它实现的类加载机制就没有完全遵循自下而上的委托,有很多平级之间的类加载器查找

/images/jvm/jvm-cl-break.png
SPI机制破坏双亲委派模型

Class文件的结构属性

/images/jvm/class-struct.png
类的结构