JVM加载class文件的具体过程
一个class文件被加载到内存中主要有三个步骤:装载、链接、初始化。其中链接又细分为三小步:验证、准备、解析,下面一次对每一步进行大致的介绍:
一,装载:
什么是装载?装载的意思是指Java虚拟机查找.class文件并生成字节流,然后根据字节流创建java.lang.Class对象的过程。这个过程里面有可以简单的分为三小步:
-
查找到.class文件,并生成二进制字节流。
-
把字节流形式的.class文件的各个部分解析成JVM的特定数据结构,并存储在方法区。
-
在内存中创建java.lang.Class对象:接下来程序运行过程中所有对该类的访问都是通过这个java.lang.Class对象提供的接口来访问。
装载的分类:
隐式装载:在程序运行过程中,碰到new等方式生成对象时,系统会隐式调用ClassLoader去装载对应的.class到内存中去
显示装载:在编写源代码时,主动调用Class.forName()等方法去主动装载.class文件到内存。
二,链接:
链接分为验证、准备和解析。
**验证:**是链接的第一步,主要是为了检验该.class文件的字节流中包含的信息是否符合虚拟机的要求,并且不会危及虚拟机本身的安全,主要包含以下几个方面的检验:
-
文件格式检验:检验字符流是否符合class文件格式规范。
-
元数据检验:对字节码描述信息进行语义分析,保证描述内容符合Java语言规范要求。
-
字节码检验:通过数据流和控制流分析,确定程序语义是否合法,符合逻辑。
-
符号引用检验:对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
准备:
为类中静态变量分配内存,并为其设置“0”值。
例如:
public static int value=100;
在准备阶段,JVM会为value分配内存,并将其值设置为0,只有当初始化时候,value才会设置其真正的值100.
在该阶段内存分配仅仅包括类变量,不包括实例变量(实例变量将在随着对象的实例化而一起分配在堆中)
而静态常量在准备阶段分配内存值是其真正的值,如:
public static final int value=100;
在准备阶段分配的值为100。
解析:
解析是链接的最后一步,这一阶段是将常量池中的符号引用转换为直接引用,也就是具体的内存地址。包括常量池中的 类、接口名、字段名、方法名等转换为具体的内存地址。
三,初始化:
这是class加载的最后一步,这一阶段是执行类构造器方法的过程,并真正初始化类变量。比如:
public static int value=100;
在准备阶段value被分配内存并设置成0,到了这一步初始化阶段设置成100。
初始化时机:
-
虚拟机启动时,初始化包含main方法的主类;
-
遇到new指令创建对象时;
-
访问静态方法或者静态字段的指令时;
-
子类的初始化过程中 如果发现父类还没有初始化时,则先初始化父类;
-
使用反射API进行调用时;
-
第一次调用java.lang.invoke.MethodHandle实例时,需要初始化MethodHandle指向方法所在类;
初始化变量:
在初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是static关键字的信息,而没有static修饰的语句块在实例化对象的时候才会执行。
初始化顺序:
静态变量/静态代码块->普通代码块->构造函数
- 父类静态变量和静态代码块;
- 子类静态变量和静态代码块;
- 父类普通成员变量和普通代码块;
- 父类的构造函数;
- 子类普通成员变量和普通代码块;
- 子类的构造函数;