- 类加载器(ClassLoader):将字节码文件加载到内存中
- 运行时数据区(JVM管理的内存):负责管理JVM使用的内存,比如创建对象和销毁对象
- 执行引擎:即时编译器、解释器、垃圾回收器。负责本地接口的调用
- 本地接口:native方法,使用的是C++编写
一、 字节码文件组成
常量池和方法
常量池的作用:避免相同内容重复定义,节省空间。在常量池中存放一份数据,在其他地方引用。
常量池中的数据都有一个编号,编号从1开始,在字段或字节码指令中通过编号可以快速的找到对应的数据。
符号引用:字节码指令中通过编号引用到常量池的过程
存在两个数据结构:操作数栈和局部变量表
二、 类的生命周期
类的生命周期描述了一个类从类加载、使用、卸载的整个过程
1. 加载
加载阶段是类加载器根据类的全类名通过不同的渠道以二进制流的方式获取字节码信息
类加载完成后会将字节码信息保存至方法区,生成一个InstanceKlass对象,保存类的所有信息,里面还包含实现特定功能比如多态的信息。(C++编写)
同时JAVA虚拟机还会在堆中生成一份与方法区中数据类似的java.class.Class对象。作用是在Java代码中获取类的信息以及存储的静态字段的数据(JDK8之后)(JAVA编写)
方法区和堆中分别有一份数据,这样的话,JVM就可以很好的控制开发者的访问权限。
2.连接
2.1 验证
验证字节码信息是否满足《Java虚拟机规范》,比如对头魔数的校验(ca fe ba be)、版本号等的校验、元信息的校验(必须有父类)、指令是否正确等
2.2 准备
为静态(static)变量分配内存并设置初始值,针对静态常量(static final)则会在该阶段直接进行赋值。
准备阶段只会给静态变量赋初始值,而每一种基本数据类型和引用类型都有其初始值。
数据类型 | 初始值 |
---|---|
int | 0 |
long | 0L |
short | 0 |
char | '\u0000' |
byte | 0 |
boolean | false |
double | 0.0 |
引用数据类型 | null |
final修饰的基本数据类型的静态变量,准备阶段会直接将代码中的值进行赋值。
这样的话在修改final的值后,必须重新编译,不能直接更新运行
2.3 解析
将常量池中的符号引用替换成指向内存的直接引用。
符号引用:使用常量池中的编号进行访问
直接引用:使用内存中的地址访问
3.初始化
init:构造方法时执行
mian:Main方法
clinit:初始化阶段执行
主要操作:执行静态代码块和为静态变量赋值
该阶段会执行方法中的clinit方法中的部分指令,clinit方法是与JAVA代码中的方法顺序执行一致。
3.1 类初始化的几种方式
- 访问一个类的静态变量或者静态方法(如果变量是final修饰的并且等号右边是常量不会触发初始化,因为在连接阶段会直接赋常量值)
- 调用Class.forName(name)方法
- new对象时
- 执行Main方法的当前类。
3.2 clinit()不会出现的几种方式
- 无静态代码块或者无静态变量赋值语句
- 有静态变量的声明,但是没有赋值语句
- 静态变量中使用final修饰,该常量在连接-准备阶段进行赋值
3.3 注意
- 直接访问父类的静态变量,只初始化父类,不会触发子类的初始化
- 子类初始化clinit之前会先调用父类的clinit方法
- 数组的创建不会导致数组中元素的类进行初始化。(Demo[] demos = new Demo[10]),并不会初始化Demo类
- final修饰的常量,如果赋值的内容需要执行指令才能够得出结果,则会在clinit方法时进行初始化(private static final int i = Integer.valueOf(10))
4.使用
5.卸载
垃圾回收中实现