Java类加载机制和双亲委派模型

笔记目录

  • 1.类加载机制
  • 2.类的生命周期 / 类加载过程
    • 2.1 类的生命周期总览(7步,加载5步)
      • 2.1.1 阶段顺序
      • 2.1.2 加载的时机
      • 2.1.3 类加载是线程安全的
    • 2.2 加载 loading
    • 2.3 校验 / 验证 verification
      • 2.3.1 文件格式验证
      • 2.3.2 元数据验证
      • 2.3.3 字节码验证
      • 2.3.4 符号引用验证(在解析阶段完成)
    • 2.4 准备 preparation
    • 2.5 解析 resolution
    • 2.6 初始化 initialzation
  • 3.类加载器
    • 3.1 引导类加载器 Bootstrap ClassLoader
    • 3.2 扩展类加载器 Extention ClassLoader
    • 3.3 应用类加载器 App ClassLoader
    • 3.4 自定义类加载器 Custom ClassLoader
    • 3.5 JVM类加载器的问题
  • 4.双亲委派机制
    • 4.1 为什么要设计双亲委派模型?
  • 5.自定义类加载器
  • 6.打破双亲委派机制(Tomcat为例)
    • 6.1 CommonClassLoader
    • 6.2 CatalinaClassLoader
    • 6.3 SharedClassLoader
    • 6.4 WebappClassLoader

1.类加载机制

我们在使用一个Java类的时候,比如使用累的静态属性或者new一个对象,JVM虚拟机会将我们目标类的.class文件加载到JVM特定的内存区域,然后经过一系列的加载流程,最后我们的程序才能够使用,这整个流程称之为类加载机制,也就是说我们要使用一个类,必须先加载才能使用。当然,一个类的声明周期不单单只是加载,还有许多阶段,例如初始化、卸载等操作。
JVM类加载是通过一系列的类加载器进行实现,类加载器具有加载一个.class的文件能力,不同的类加载器有着不同的效果。
Java类加载机制和双亲委派模型_第1张图片
如图所示,我们要使用自己写的com.minor.test类,则JVM需要经历很多阶段,每个阶段很重要。

2.类的生命周期 / 类加载过程

2.1 类的生命周期总览(7步,加载5步)

Java类加载机制和双亲委派模型_第2张图片

2.1.1 阶段顺序

类加载的5步中,加载、校验、准备、初始化这几个阶段是严格顺序的,但是解析阶段不一定,因为Java支持运行时动态解析绑定(静态绑定、动态绑定)。

2.1.2 加载的时机

懒加载是JVM加载类的一个策略,当程序需要用到类的时候才回去检查和加载。

2.1.3 类加载是线程安全的

JVM会保证一个类的加载时线程安全的。

2.2 加载 loading

     1. 根据要加载的类的全限定名获取到该类的二进制文件,通过磁盘I/O转化为字节流。
     1. 将字节流信息结构转化为方法区的运行时数据结构。
     1. 在JVM堆内存中创建一个该类的`java.lang.Class`对象,作为方法区类元数据的访问入口。

2.3 校验 / 验证 verification

校验阶段是类加载连接阶段的第一步,目的是检查class文件是否符合JVM规范要求。其中,校验阶段一共分为4个校验点

2.3.1 文件格式验证

验证clsss文件字节流是否符合JVM规范,并支持当前JVM版本,例如:

        - 文件是否是cafe babe开头
        - 主版本号和副版本号是否支持当前JVM版本
        - 常量是否有不符合格式的定义,等等...

2.3.2 元数据验证

字节码文件描述信息的语义是否符合Java语言语法规范,例如:

        - 这个类是否继承了final修饰的类
        - 是否是抽象类,是的话检查是否实现了父类或接口的方法
        - 重写、重载等是否有语法错误,等等...

2.3.3 字节码验证

通过数据流分析、控制流分析,判断程序语义是否合法,保证不会危害JVM安全,例如:

        - 跳转指令符合跳转规则
        - 类型转换是否符合Java规范,等等...

2.3.4 符号引用验证(在解析阶段完成)

检查符合引用的合法性,例如符号引用是否能指向目标、符号引用是否可访问等问题

2.4 准备 preparation

准备阶段会为类中定义的静态变量分配内存并设置初始值,基本类型就赋值默认值,引用类型就是null。
但是,如果是final修饰的静态变量,且是基本类型和String且赋值时字面量形式,在准备阶段会赋值真实值,如果不是字面量形式,那么会在初始化赋值。

2.5 解析 resolution

解析阶段是将JVM常量池类信息的符号引用替换为直接引用过程,该阶段会将一些静态方法替换为指向数据所真实存在的内存地址,称之为静态链接 or 静态绑定动态绑定则是在程序运行时动态完成。

2.6 初始化 initialzation

初始化阶段主要是对class中定义的静态块和静态变量赋予真实值。JVM规范中定义了6种情况必须对类进行初始化操作,包括:

        1. 使用new关键字实例化对象时,读取 or 设置一个类的静态变量时,调用静态方法时。
        1. 使用java.lang.reflect包进行反射时。
        1. 发现父类没有初始化时,会初始化父类。
        1. 程序启动类main方法所在类,严格触发初始化。
        1. JDK7后,使用java.lang.invoke.MethodHandle句柄操作类时。
        1. 当接口中存在default修饰符时,且实现类发生了初始化时。

3.类加载器

类加载过程的执行者就是一个称为类加载器的东西处理的(5个步骤),JDK提供了三层类加载器:

3.1 引导类加载器 Bootstrap ClassLoader

任何类加载的行为都要经过它,他负责加载java核心类库:rt.jar、resources.jar、charsets.jar等,也可以通过JVM指令进行指定加载路径:-Xbootclasspath,我们在Java程序中打印rt.jar包下面的类的加载器是显示null,是因为这个引导类加载器是C++语言构造。

3.2 扩展类加载器 Extention ClassLoader

主要用户加载lib/ext包下的jar包和.class文件,它继承自URLClassLoader

3.3 应用类加载器 App ClassLoader

用来加载工程classpath下的jar包和.class文件。

3.4 自定义类加载器 Custom ClassLoader

负责加载用户自定义路劲的jar包和.class文件。

3.5 JVM类加载器的问题

如果在java.lang包下,再自己写一个String类,其实自己写的String类并不会生效,除非放在我们自己的classpath下才能生效。这是因为java的类加载机制提供了一个双亲委派的模型来保证核心类库的安全性,不被随意修改,因为同一个类被两个不同的类加载器加载就会是不同的类了。

4.双亲委派机制

Java类加载机制和双亲委派模型_第3张图片

双亲委派机制的简要流程是一个递归的过程,如下:
1.AppClassLoader检查该类是否已经被加载findLoadClass(),如果又返回,则不用加载,直接返回。
2.如果没有被加载过,则判断是否有父加载器父加载器不是父类,他们没有继承关系,如有有则委托父 加载器加载loadClass()
3.如果父加载器加载不了,则调用当前类加载器进行加载。

4.1 为什么要设计双亲委派模型?

1.保证安全,防止JDK核心类库不会被篡改。
2.避免重复加载,保证类的唯一性。

5.自定义类加载器

自定义一个类加载器,只需要继承java.lang.ClassLader类,并重写findClass()

6.打破双亲委派机制(Tomcat为例)

可以通过覆盖loadClass()进行双亲委派的自定义。但是打破双亲委派是在ExtClassLoader之下的类加载器,JDK核心包仍然是不能打破的,会抛出package java权限错误。
例如Tomcat就是打破双亲委派机制典型的代表:
Java类加载机制和双亲委派模型_第4张图片

6.1 CommonClassLoader

Tomcat最基本的类加载器,加载各个webapp应用公用库的加载。

6.2 CatalinaClassLoader

对webapp应用不可见。

6.3 SharedClassLoader

对tomcat容器和catalina不可见,对所有webapp应用可见。

6.4 WebappClassLoader

各个应用的类加载器,各个应用的类相互隔离,每个WebappClassLoader加载自己目录的class文件和jar,并不会委托给父加载器,打破了双亲委派模

如果Tomcat使用的是Java默认的双亲委派模型,那么会有如下问题:
1.无法加载两个全限定名一样的类,实现不了多版本的class加载问题。
2.web容器和应用程序的类库容易混淆,不安全。

你可能感兴趣的:(JVM,java,jvm,后端)