类加载的过程
Java虚拟机中类加载的过程分为加载、验证、准备、解析、初始化五个阶段。
加载
“加载”是“类加载”过程的一个阶段,希望读者不要混淆这两个看起来很像的名词。加载阶段,虚拟机需完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
虚拟机规范的这3点要求其实并不算具体,因为虚拟机实现与具体应用的灵活度都是相当大的。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段是否严谨,直接决定了Java虚拟机能否承受恶意代码的攻击,从执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载系统中又占了相当大的一部分。
验证阶段分为四个阶段的校验动作:
1,文件格式验证
1)是否以魔数0xCAFEBABE开头。
2)主次版本号是否在当前虚拟机处理范围之内。
3)常量池的常量是否有不被支持的常量类型。
4)指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
5)CONSTANT_Utf8_info型的常量是否有不符合UTF8编码的数据。
6)Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
…
2,元数据验证
1)这个类是否有父类(除了java.lang.Object,其他类都应当有父类)
2)这个类的父类是否继承了不允许被继承的类(final修饰的类)
3)如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法
4)类中的字段、方法是否与父类产生矛盾。
…
3,字节码验证
1)保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作。
2)保证字节跳转指令不会跳转到方法体以外的字节码指令上。
3)保证方法体中的类型转换是有效的。。
…
4,符号引用验证
1)符号引用中通过字符串描述的全限定名是否能找到对应的类。
2)在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
3)符合引用中的类,字段,方法的访问性是否被当前类访问。
…
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的时候仅包括类变量,不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。还有,若一个类变量定义如下:
public static int value =123;
那变量在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后存放与类构造器()方法之中,所以value赋值为123的动作将在初始化阶段才会执行。
解析
将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。
可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)
初始化
将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量,那么就会使用用户指定的值覆盖之前在准备阶段设置的初始值;如果执行的是static代码块,那么在初始化阶段,JVM就会执行static代码块中定义的所有操作。
所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是方法,即类/接口初始化方法。该方法的作用就是初始化一个中的变量,使用用户指定的值覆盖之前在准备阶段里设定的初始值。任何invoke之类的字节码都无法调用方法,因为该方法只能在类加载的过程中由JVM调用。
如果父类还没有被初始化,那么优先对父类初始化,但在方法内部不会显示调用父类的方法,由JVM负责保证一个类的方法执行之前,它的父类方法已经被执行。
JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。
参考文献:《深入理解java虚拟机》