前言
我们知道Java类的生命周期主要包括以下几个过程,加载
、验证
、准备
、解析
、初始化
、使用
以及卸载
,其中验证
、准备
以及解析
三个过程合起来也称为链接
,但每个过程具体做了哪些事情呢?这里我们就详细解释下;
生命周期理解
加载
加载:
所谓类加载,实际上指的是,将一个Java类的字节码文件加载到机器内存中(方法区),且在内存中构建出一个具体的实例对象(该数据处于运行时数据区-方法区),该实例对象的模型为java.lang.Class类;
加载阶段必须要完成的三件事:
1. 通过类的全限定名获取类的二进制数据流;
2. 解析类的二进制数据类型到方法区中;
3. 创建一个java.lang.Class类的实例,表示该模型,作为方法区这个类的各种数据的访问入口;
二进制流的使用方式
概述:
只要是符合JVM规范内容的二进制数据流都可;
二进制流的一般渠道:
- 虚拟机可以通过文件系统去读入一个class后缀名的文件;
- 读入jar、zip等压缩包;
- 实现存放在数据库中的二进制数据;
- 使用类似于http网络进行通信数据;
- 运行时生成一段class的二进制信息;
以上渠道获取到的二进制数据获取后会转换成一个java.lang.Class实例;如果不符合规范,则会转换成ClassFormatError异常抛出
;
类模型和Class实例位置
- 类的模板数据存放在方法区;
- 实例位置:类将.class文件加载至
元空间
后,会在堆中创建一个java.lang.Class对象,用来封装类位于方法区内的数据结构,该对象是在加载类的过程中创建的,每个类都对应有一个Class类型的对象;
数组类的加载:
创建数组类的情况有些特殊,因为数组本身并不是由类加载器负责创建,而是由JVM在运行时根据需要直接创建,但数组的元素类型仍需要去类加载;
数组类的创建过程:
3. 如果数组是引用数据类型数组,那么遵循加载过程中的递归再加载创建;
4. JVM使用指定元素类型和数组维度来创建新的数组类;
链接
- 验证
目的:
保证加载的字节码是合法的,符合规范的;
步骤:
- 准备
目的:
为类的静态变量分配内存,并为其初始化默认值;
注意点:
- 这里不包含基本数据类型的字段用static final的情况,final修饰会在编译期就分配,准备阶段是赋值;
- 不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配在堆中;
- 这个阶段不会有代码执行;
- 解析
目的:
将类、接口、字段、方法的符合引用转换为直接引用;
如System.out.println() ----->对应字节码:invokevirtual #24<java.io.PrintStream.println>
初始化
目的:
给相关的变量去赋初始值;
初始化最重要的工作就是执行初始化方法<cinit()>
,该方法由编译器生成并且调用,不能在Java程序中人为调用,<cinit()>
方法里包含类的静态成员与静态代码块的执行字节码;
使用
主动使用的执行取决于上述初始化阶段的<cinit()>
方法是否被调用的情况;
主动使用场景:
- 创建一个类的实例,使用new、反射、克隆、反序列化等操作;
- 当使用类、接口的静态字段及静态方法时;
- 使用反射类时,Class.forName(“com.xxx.xxx”)
- 子类初始化时,需要先触发父类的初始化;
- main方法所在的类;
- …
被动使用场景:
7. 当访问一个静态字段时,只有正在使用的这个字段的类才会被初始化;
8. 通过数组定义类引用,不会触发此类的初始化;
9. 引用常量不会触发此类或者接口的初始化,因为常量在链接阶段已经被赋值;
10.调用ClassLoader类的loadClass()
方法加载一个类,并不是对类的主动使用,不会导致类的初始化;
卸载
条件:
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。如果有反射获取到了某个类的方法,那么这个类不能卸载。
Java类加载器的分类
系统类加载器
,主要加载jre基础包内容;扩展类加载器
,主要加载扩展包内容;应用程序类加载器
,加载当前系统应用内容;自定义类加载器
,默认没有,自定义开发,自行自定要加载的内容;
而Android中类加载器属于自定义类加载器
!
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )