JVM想要执行Java程序必须先将程序对应类的字节码文件加载到虚拟机内存中,本篇就来讲一下类的加载流程
类的加载流程分为加载,连接,初始化三个阶段,而连接阶段又可再分为验证,准备,解析三个阶段,其中加载,验证,准备这三个阶段的执行顺序是固定的,类的加载必须按照这个顺序执行,但是解析阶段解析阶段有可能在初始化之后执行,这是因为Java的支持运行时绑定。
注意:以上流程虽然按照顺序依次开始执行,但不代表是依次结束执行的,可以看成是各个阶段交叉执行。
1. 类在什么时候会被加载
由于Java虚拟机并没有对加载阶段最明确的规定,但是对于初始化阶段,Java虚拟机规定在执行以下操作时会对类进行初始化(在此之前必定会进行加载,验证,准备):
- new一个类对象,调用类的静态变量,调用类的静态非字面量常量(静态字面量常量在准备阶段就可被赋予指定的值,不需要初始化),调用类的静态方法时。
- 子类进行初始化时,如果父类未初始化,会先初始化父类
- 对类进行反射调用时
- 当初程序的main方法被执行时,main方法所在类被初始化
2.加载阶段
类加载阶段主要执行以下操作: 类加载器根据全类名将对应的字节码文件加载到方法区中,根据方法区中的字节码文件在堆内存中new一个对应类的java.lang.Class对象作为访问方法区的类数据的入口。
字节码文件的来源
- 从本地硬盘读取
- 从网络接收
- 从jar或者zip中加载
- 从专有的数据库中读取
- 将Java源码动态编译为字节码
加载阶段对于开发人员来说是可操作性最高的阶段,因为这个阶段我们既可以使用自带的类加载器,也可以使用自定义的类加载器
3.连接阶段
1. 验证:
对加载的Class文件字节流进行验证,是否符合字节码文件格式的规范,虚拟机的规范,符合Java语法规范等,主要包含:
- 元数据验证
- 符号引用验证
- 文件格式验证
- 字节码验证
2.准备:
为静态变量分配内存并赋默认值(0,null,false等)
为静态字面量常量分配内存并赋予指定的值(这里不是默认值而是代码里定义的值,如果不是字面量和静态变量一样赋默认值)
3.解析
将常量池中的符号引用转化为直接引用(内存地址), 符号引用是在编译期生成的,放在常量池中,加载到虚拟机后放在方法区的运行时常量池中
- 为什么要有符号引用: 主要是因为我们将一个类的源代码文件通过编译器编译成字节码文件时,这个类可能还依赖了很多其他的类,但是我们不可能一次性把其他的类也编译到这个文件中,所以我们这里只需要用符号描述我们引用了哪些类即可,这就是符号引用.
4.初始化阶段
- 对类的静态变量赋值初始值(代码里定义的值)。
- 可以在定义静态变量时指定值,也可以在静态代码块内为静态变量指定值。
- 初始化时会调用cinit方法进行初始化,cinit方法则是根据类里面定义的静态变量和静态代码块生成的,如果类中没有静态变量和静态代码块,则不生成cinit方法。
在这里顺便说一下init方法,init方法是根据构造方法生成的,在创建类的实例时调用,并且无论有没有显式定义构造方法,类被编译后都会生成init方法,因为有默认的无参构造方法。