Java类加载机制

Posted Tim_Bergling

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java类加载机制相关的知识,希望对你有一定的参考价值。

Java类加载机制

简介

类的加载:
将类的.class文件中的二进制数据读取到内存中,将其放在运行时数据区的方法区中,在堆内存中创建一个java.lang.Class对象,用来封装在方法区内的数据结构。

Class对象:
封装类在方法区内的数据结构,提供访问方法区的数据结构的接口。

注:不需要等到某个类被“首次使用”再加载,允许预先加载。

加载.class文件的方式:

  • 本地系统中直接加载
  • zip,jar文件中加载
  • Java源文件动态编译

类的生命周期

类加载的过程:

  1. 加载(loading)
  2. 连接
    • 验证(verification)
    • 准备(preparation)
    • 解析(resolution)
  3. 初始化(initialization)
  4. 使用(using)
  5. 卸载(unloading)

注:

  • 加载,验证,准备和初始化发生的顺序是确定的,而解析阶段则不一定。
  • 解析可能发生在初始化之后(运行时绑定/动态绑定/晚期绑定)
  • 这些阶段的先后顺序是开始的时机,不代表运行或完成的时机(有可能先开始的后完成)。

加载

查找并加载类的二进制数据。

功能:

  • 通过类的全限定名来获取其定义的二进制字节流。
  • 将字节流代表的静态存储结构转换为方法区的运行时数据机构。
  • 在堆内存中生成一个代表该类的java.lang.Class对象,作为该数据的访问入口。

连接

验证

确保被加载的类的正确性。(非必要)

行为

  • 文件格式验证:验证字节流是否符合Class文件格式的规范。
  • 元数据验证:对字节码描述的信息进行语义分析。
  • 字节码验证:通过数据流和控制流分子,确定程序语义是否合法,符合逻辑。
  • 符号引用验证:确保解析动作正确执行。

准备

为类的静态变量分配内存(方法区),并将其初始化为默认值。

注意

  • 内存分配只包括类变量(static)(实例变量:对象实例化随着对象分配在堆内存中).
  • 设置的初始值为数据类型默认的零值.
  • 若类字段的字段属性为static final,则被初始化为所指定的值,而非默认零值.
  • 对于基本数据类型:类变量(static)和全局变量,若不显式赋值则默认为零值.
  • 对于局部变量:使用前必须显式赋值,否则无法通过.
  • static final修饰的常量必须在声明时赋值;final修饰的变量可以声明时赋值,也可以类初始化显式赋值.
  • 引用类型未显式赋值,则默认为null.
  • 数组初始化为赋值,则其中每个元素赋值对应数据类型的零值.

解析

把常量池内的符号引用替换为直接引用的过程.

针对:类或接口,字段,类方法,接口符号,方法类型,方法句柄和调用点限定符.

符号引用:一组符号来描述目标,可以是任何字面量.

直接引用:直接指向目标的指针,相对偏移量或间接定位到目标的句柄.

初始化

为类的静态变量赋予正确的初始值.

设定方式:

  • 声明类变量为指定初始值.
  • 使用静态代码块为类变量指定初始值.

初始化步骤

  • 若类没有被加载和连接,则先加载并连接该类.
  • 若类的直接父类没有被初始化,则先初始化其直接父类.
  • 若类中有初始化语句,则依次执行初始化语句.

初始化时机

类被主动使用时导致类的初始化.

  • 创建类的实例(new Object()).
  • 访问类或接口的静态变量.
  • 调用类的静态方法.
  • 反射(Class.forName("io.github.truestoriesavici01.Test"))
  • 初始化某个类的子类,其父类也被初始化.
  • 虚拟机启动时被表明为启动类的类.

结束

  • 执行System.exit()方法.
  • 程序正常结束.
  • 程序执行时遇到异常或错误而终止.
  • 操作系统导致的虚拟机进程终止.

类加载器

分类

  • 启动类加载器(Bootstrap ClassLoader):负责加载\\lib目录或指定的可识别的类库,无法被直接引用.
  • 扩展类加载器(Extension ClassLoader):由ExtClassLoader实现,加载\\ext目录或指定的类库,可直接使用.
  • 应用程序类加载器(Application ClassLoader):由AppClassLoader实现,负责加载用户类路径(ClassPath)指定的类.

注:三种类加载器相互配合进行加载的.

JVM类加载机制

  • 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖或引用的其他Class也将由这个类加载器负责载入.
  • 父类委托:先让父类加载器试图加载该类,若父类加载器无法加载该类时才试图从自己的类路径中加载该类.
  • 缓存机制:所有加载过的Class都会被缓存.当程序使用Class时,先从缓存中寻找该Class.若不存在,才读取该类对应的二进制数据,转换为Class对象,存入缓存中.

类的加载方式

  1. 命令行启动应用时由JVM初始化加载.
  2. 通过Class.forName()方法动态加载.
  3. 通过ClassLoader.loadClass()方法动态加载.

Class.forName()ClassLoader.loadClass()的区别

  • Class.forName():将类的.class文件加载到JVM中,会类进行解释,执行类中的static块.
  • ClassLoader.loadClass():将.class文件加载到JVM中.

双亲委派模型

简介

当一个类加载器收到类加载请求时,不会自己加载该类,而是委托父加载器.依次向上发出请求,直到传递到顶层的启动类加载器.只有当父加载器没有找到所需的类,无法完成加载时,子加载器才试图自己加载该类.

流程

  1. 当AppClassLoader加载一个class时,将类加载请求委派给父类加载器ExtClassLoader去完成.
  2. 当ExtClassLoader加载一个class时,先将类加载请求委托给BootStrapClassLoader去完成.
  3. 若BootStrapClassLoader加载失败,会使用ExtClassLoader尝试加载.
  4. 若ExtClassLoader也加载失败,则使用AppClassLoader加载,若加载失败,会报出异常ClassNotFountException.

优势

  • 防止内存中出现多份同样的字节码.
  • 保证Java程序安全稳定运行.

自定义类加载器

  • 继承自ClassLoader类.
  • 重写findClass方法.

参考:

以上是关于Java类加载机制的主要内容,如果未能解决你的问题,请参考以下文章

[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的

JAVA类加载机制详解

Java类加载机制

JAVA 类加载机制

详解JAVA类加载机制

Java虚拟机类加载机制