类的加载过程
一.概述:
Java中数据类型分为基本数据类型和引用数据类型,基本数据类型由Java虚拟机预先定义,引用数据类型则需要类的加载(类,接口,枚举,注解)
二.类的生命周期主要包括如下7个阶段:
加载(Loading) –> [{验证(Verification) –> 准备(Preparation) –> 解析(Resolution)}
链接(Linking)] –> 初始化(Initialization)–> 使用(Using)–> 卸载(Unloading)
其中验证,准备,解析3各部分统称为链接
三.类的加载:
简而言之就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型—类模板对象。所谓的类模板对象,其实就是Java类在JVM内存中的一个快照,JVM将从字节码中解析出的常量池,类字段,类方法等信息存储到类模板中,这样在JVM运行期间能够通过类模板获取到java类的任何信息和进行方法调用等操作,反射的机制即基于这一基础。如果JVM没有将java类的声明信息存储起来,则JVM运行期间无法进行反射。
类加载结束后的效果:堆中创建并存储该类对象实例数据,方法区(Hostport虚拟机–jdk7以前永久代–jdk8
元空间)中创建类该Class对象的引用并指向堆中该类对象的实例数据
四.链接—验证阶段:
当类加载到系统后,就开始连接操作,验证是链接的第一步。它的目的是保证加载的字节码是合法的,合理并符合规范的。验证的步骤比较复杂,大体上java虚拟机需要做以下步骤的检查:
格式检查(该检查其实在加载阶段已经完成)
语义检查
字节码验证
符号引用验证
五.链接—准备阶段:
简而言之就是为类的静态变量分配内存,并将其初始化为默认值。
注意:非final修饰的变量,在准备环节进行默认初始化赋值。 final修饰以后,在编译阶段进行初始化赋值,在准备环节直接进行显示赋值。如果使用字面量的方式定义一个字符串的常量的话,也是在准备环节直接进行显示赋值。
1 | public static final String constStr = "CONST"; // 准备阶段显示赋值 |
六.链接—解析阶段:
简而言之就是将类,接口,字段和方法的符号引用转为直接引用
注意:链接阶段中解析操作往往伴随着JVM在执行初始化之后才再执行
七.初始化:
简而言之就是为类的静态变量赋予正确的初始值,初始化阶段的重要工作是执行类的初始化方法及静态代码块。
八.使用static + final修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值?
1 | 情况1:在链接阶段的准备环节赋值 |
九.
虚拟机会保证一个类的
执行这个类的
但是需要注意以下会发生死锁的情况:
1 | // 初始化线程A需要初始化B |
十.类的主动使用:意味着会调用类的
1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
3. 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。(对应访问变量、赋值变量操作)
4. 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName(“com.atguigu.java.Test”)
5. 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
7. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)
补充说明:
当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。
>在初始化一个类时,并不会先初始化它所实现的接口
>在初始化一个接口时,并不会先初始化它的父接口
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化。
十一.类的被动使用:即不会进行类的初始化操作,即不会调用
1. 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。
> 当通过子类引用父类的静态变量,不会导致子类初始化
2. 通过数组定义类引用,不会触发此类的初始化
3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。
4. 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
说明:没有初始化的类,不意味着没有加载!