一、流程
Java 源码需要先进行编译,将编译之后所得的字节码文件交给 Java 虚拟机进行解析,将字节码转换成当前平台(Windows、Mac、Linux…)能识别的机器指令。
也就是说要想执行下去,首先得获取 Class 文件,而获取 Class 文件的过程,便是所谓的类加载
类加载指的是将源码( Xx.java )经过 Javac 编译之后所得到的字节码文件( Xx.class )从硬盘上加载到内存的过程。
类加载主要三步:加载 Loading
、连接 Linking
和 初始化 Initialization
其中连接又分为验证、准备和解析三个阶段。
二、加载
利用类加载器将 class 字节码文件加载并解析到内存,将字节码中的信息转变为方法区的运行时数据结构,方法区会存储包括类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据,并且会在堆上生成一个代表该类的 Class 对象,作为方法区数据的访问入口
三、连接
将字节码文件合并到 JVM 的运行环境之中(JRE)
注意
:加载和连接是交叉进行的,连接可能在加载还未结束便开始了
3.1 验证
目的
:确保 Class 字节码文件中的信息符合 JVM 的规范,确保这些信息执行后不会存在安全问题、危害虚拟机。
主要由四个检验阶段组成
文件格式验证
- 对 Class 文件格式的检查,确保符合 Class 文件的规范
- 基于 Class 文件进行
元数据验证
- 对字节码语义的检查
- 比如:是否继承了不能继承的类…
- 基于方法区的存储结构进行
字节码验证
- 对程序语义的检查
- 比如:参数是否正确、对象转型是否正确、
- 基于方法区的存储结构进行
符号引用验证
- 对类的正确性的检查
- 比如:引用其他类、方法是否存在,是否能正常访问
- 基于方法区的存储结构进行
3.2 准备
在方法区中为类变量(被 static 修饰的)
分配内存,并设置初始值,如果未手动设置,则将变量的类型对应的默认值作为初始值,反之则是由手动设置的作为初始值
3.3 解析
虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
换句话来说,就是得到类或者字段、方法在内存中的指针或者偏移量,能直接定位到虚拟机,内存中具体的位置,从而使得方法可以被调用。
主要针对:类或接口
、字段
、类方法
、接口方法
、方法类型
、方法句柄
和调用限定符
7 类符号引用进行。
四、初始化
源码被编译后,会自动生成一个
类构造器< clinit > ()
方法(类构造器是构造类的信息而不是构造对象的构造器)
初始化便是执行初始化方法 < clinit >() 方法进行初始化的的过程,此时才是正在执行字节码。如果父类没有初始化,会先触发父类的初始化,然后才是子类。
特点
- 虚拟机会保证一个类的 < clinit > () 方法在多线程的环境下被正确加锁和同步,保证的安全性。( < clinit > () 带锁)
4.1 主动引用
一定会发生类的初始化
- 虚拟机启动,初始化 main 方法的类
- new 对象
- 调用类的静态成员(除 final 常量)和静态方法
- 对类进行反射调用
4.2 被动引用
不会发生类的初始化
- 访问一个静态域时,只有定义该静态域的类会被初始化,如通过子类访问父类的静态变量,子类不会初始化
- 引用常量不会触发类的初始化
- 数组定义类引用不会触发对应类的初始化