通过之前的学习,我们知道:类加载就是根据一个类的全限定名,将其字节码文件 加载到JVM内部,并创建java.lang.Class对象实例。其实,一个类的加载过程应包括:加载,连接(验证,准备,解析),初始化。
加载:
通过类加载器,根据一个类的全限定名,将该类的字节码文件加载到JVM中(存储在方法区内),再创建java.lang.Class对象实例,由于JVM规范中并没有要求Class对象实例应该是否应该存放在堆区中,在HotSpot VM中将Class对象实例存储在方法区内。该方法区中的Class对象实例,就会作为日后访问该类数据的入口。
连接:
连接是将JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。连接阶段又是由:验证,准备,解析 3个阶段组成。
- 验证:主要任务是验证类的数据信息是否符合JVM的规范,是否是一个有效的字节码文件;
- 准备:主要是给类中的静态变量分配内存空间,并赋初始值;
- 解析:主要任务是将常量池中的所有符号引用变成直接引用;
注意:Java虚拟机中并没有明确要求解析阶段必须得在初始化之后,因此可以先进行类的初始化阶段,在进行解析阶段。
初始化:
该阶段中,JVM会把程序中所有标识static关键字的语句全部执行一遍,会覆盖在连接的准备阶段所赋的初始值,在初始化阶段 JVM还会执行static块中的所有操作。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
小总结:类的加载总共包含三个阶段:加载,连接,初始化。
- 加载:由类加载器根据类的全限定名,采用双亲委派机制将类的字节码文件加载进JVM中,并创建Class实例对象。
- 连接:该阶段主要是将,加载进JVM中的二进制字节流数据的类信息合并到JVM的运行时中来,其中包括三个阶段:
验证:检查类的数据信息是否满足JVM的规定,以及二进制字节码文件是否满足规定;
准备:给类中的static静态变量分配内存空间,并赋初始值;
解析:将常量池中的所有符号引用全部变成直接引用。
3.初始化:执行类中的所有static语句,也包括static块。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
初始化操作的时机选择:
Java虚拟机对加载和连接的时机选择有很大的灵活性,但是对于初始化,却有着规范限制,一个类或者接口应该在第一次主动使用时完成初始化操作,具体包括:
- 在给一个类创建新对象时:包括new,反射,序列化。
- 调用一个的静态方法时;
- 调用Java API中的反射方法时;
- 初始化一个派生类时(其超类必须已经完成初始化操作)接口中并不适合 因为当调用超接口的非静态字段时 必须完成初始化操作;
- JVM启动包含main方法的类时;
- 调用一个类或者接口的静态字段时,并给这些静态字段进行赋值操作;
public class Test { static{ //构造方法被执行 new Test(); } static int v1; static int v2=0; public Test(){ v1=10; v2=10; System.out.println("构造函数被执行"); System.out.println("v1"+v1); //10 System.out.println("v2"+v2); //10 } public static void main(String[] args) { System.out.println("v1"+v1); //10 System.out.println("v2"+v2); //0 } }
代码分析:static代码块是按顺序进行执行的,后来的值也会覆盖原来的值。