类的加载过程介绍
-
介绍
类的加载指的是将类的 .class 文件中的二进制数据读入到 JVM 内存中,将其放在运行时数据区的 方法区 内,然后在 堆区 创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。类的加载的最终是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且提供了访问方法区内的数据结构的接口。
类的加载过程分为 3 个步骤:加载;连接(验证、准备、解析);初始化,一般情况下 JVM 会连续完成 3 个步骤,有时也会只完成前两步。
-
如图
类加载过程.jpg
-
类加载器介绍
系统可能在第一次使用某一个类时,加载该类,但也可能采用 预先加载机制 来加载该类,不管怎样类的加载必须由 类加载器 来完成。通常类加载器是由 JVM 提供。
-
类的加载必须由类加载器完成,通常情况下类加载器由 JVM 提供,但也可以通过自定义。
JVM 提供的类加载器被称之为 系统类加载器
开发者还可以通过继承 ClassLoader 接口来创建 自定义类加载器
-
通过不同的类加载器,可以从不同的 "来源" 加载类的 .class 文件(二进制文件)
从本地系统中直接读取 .class 文件,大部分的加载方式。
从 ZIP、JAR 等归档文件中加载 .class 文件,很常见。
从网络下载 .class 文件数据。
从专有数据库中读取 .class 文件
将 Java 的源文件数据,上传到服务器中,进行动态编译产生 .class 文件,并加以执行。
-
但是不管 .class 文件数据来源何处,加载的结果都是相同的
- 将字节码文件数据加载到 JVM 内存中,将其放在运行时数据区的 方法区 内,然后在 堆区 创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。类的加载的最终是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且提供了访问方法区内的数据结构的接口。
加载
-
作用
- 通过一个类的全限定名来获取其定义的字节码(二进制字节流),将字节码文件加载到 JVM 内存中,此过程由类加载器完成(可控)
-
特点
- 加载阶段是可控性最强的阶段,既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
连接
-
验证
- 校验 .class 文件是否合法,遵循 .class 文件格式 参考地址
-
准备
-
为类变量(
static
修饰的变量)在 JVM 方法区中分配内存,并进行 默认初始化int
默认初始化为0
引用默认初始化为
null
等等
静态常量(
static final
) ,有所不同,直接在 JVM 方法去中 显示初始化
-
-
解析
- JVM 将常量池的符号引用。替换为直接(地址)引用
-
特点
- 此时在堆区中已经创建一个 java.lang.Class 对象,指向方法区中的数据
初始化
-
作用
-
主要是对类静态的类变量进行 显示初始化 参考地址
init 对非静态变量解析初始化
clinit 是 java.lang.class 类构造器对静态变量,静态代码块进行初始化
类构造器方法(clinit)由编译器收集类中所有类变量的 显示赋值和静态代码块中的语句合并产生
-
-
特点
当初始化某个类时,如果其父类没有初始化,则先触发父类的初始化动作
JVM 保证一个类的初始化,在多线中中正确加锁和同步
何时会或者不会触发类初始化动作呢?
-
介绍
- 上面已经介绍,类的加载过程分为 3 步,大部分 3 步按顺序完成,有时也会只完成前两步
-
如何区分会不会触发类的初始化
-
如表
会触发类的初始化 不会触发类的初始化 当虚拟机启动时,先初始化 main()
方法所在的类引用静态常量不会触发此类的初始化 一次 new
一个类的对象(在 JVM 中一个类的 Class 对象只有一个)当访问一个静态域时,只有真正声明该域的类才会被初始化(子类继承父类的静态变量,在子类使用该静态变量时,只有父类会初始化,子类不会初始化) 调用该类的静态变量( static final
除外,因为其在连接时已经显示初始化完成)和静态方法通过数组定义类引用时,不会触发类初始化( A[] as = new A[2]
A 是类,此时不会初始化 A类)当初始化某个类时,其父类没有被初始化时,则会先初始化其父类
-