类的生命周期
类加载大致分为加载、验证、准备、解析、初始化五个阶段,其中加载、验证、准备、初始化顺序是固定的,解析的顺序不一定。它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。并且这些阶段是顺序开始,而不是顺序执行或者完成,它们可能是交叉混合执行。
类加载
加载阶段的三个步骤:
- 通过类型全限定名找到类的二进制字节流
- 将字节流的静态存储结构转换为方法区运行时的数据结构
- 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区的数据的入口
连接
验证阶段
作用:确保加载类的正确性
这个阶段主要是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式的验证: 对魔数和jdk版本、常量池中的常量是否有不被支持的类型等进行校验
- 元数据验证:对字节码描述的信息进行语义分析
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:确保解析动作能正确执行。
验证阶段非常重要,但不是必须,如果已经反复验证通过很多次,可以使用
-Xverifynone
关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备阶段
为类的静态变量分配内存,并将其初始化为默认值
- 这里设置默认值仅类变量,不包括实例变量,实例变量会在类实例化时去分配
- 初始化默认值只是分配数据类型的默认值,如
0
、0L
、null
、false
等
pulbic static int i = 3
现阶段设置默认值只会设置0
, 而不是3
,赋值为3
应该在初始化阶段。
解析阶段
把类中的符号引用转换为直接引用
将常量池的符号引用转换为直接引用,一般包括 类
和接口
、类字段
、类方法
、接口方法
、方法类型
、方法句柄
以及调用点
限定符
直接引用
就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化
对类的静态变量赋予真正的值。
在Java中对类变量进行初始值设定有两种方式:
- 声明类变量是指定初始值
- 使用静态代码块为类变量指定初始值
JVM初始化步骤
- 类还没被加载和连接,就先加载类并连接类
- 类的直接父类还没被初始化,就先初始化它的直接父类
- 类中有初始化语句,则系统执行初始化语句
类初始化时机
只有对类主动使用时才会导致类的初始化
- 直接创建实例,即new
- 访问类或者接口的静态变量、对类的静态变量赋值
- 调用类的静态方法
- 反射
- 初始化类的子类
- Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
使用
类访问方法区内的数据结构的接口, 对象是Heap区的数据。
卸载
- System.exit()
- 正常执行结束
- 异常终止
- 系统异常导致java虚拟机停止
类加载器
类加载器的层级
- 启动类加载器:负责加载存放在JDK\jre\lib下,或者通过-Xbootclasspath 路径下能够被虚拟机识别的类库(所有java.* 开头的类),启动类加载器不能直接被程序引用
- 扩展类加载器:负责加载存放在JDK\jre\lib\ext下,或者通过java.ext.dirs 系统变量指定的路径下的所有类库,开发者可以直接使用扩展加载器。
- 应用程序加载器:负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器
启动类加载器是通过c语言实现的,其他的加载器都是通过java实现。
类加载机制
- 全盘负责 当一个类加载器加载一个class类时,这个class类的所有依赖或者引用的其他class都由它加载
- 父类委托 先让父类加载器加载,只有当父类加载器无法加载该类时,才从自己的类路径中加载该类
- 缓存机制 将加载过的类缓存起来,只有在缓存区找不到才去加载类,并放入缓冲区,这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
- 双亲委托 当一个类加载器接收到类加载的请求时,先不去自己加载这个类,依次向上,交给父类加载器,最终都回到启动类加载器,如果还加载不了,再自己去加载