深入理解JVM类加载机制
简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
下面我们具体来看类加载的过程:

类的生命周期
类从被加载到内存中开始,到卸载出内存,经历了加载、连接、初始化、使用四个阶段,其中连接又包含了验证、准备、解析三个步骤。这些步骤总体上是按照图中顺序进行的,但是Java语言本身支持运行时绑定,所以解析阶段也可以是在初始化之后进行的。以上顺序都只是说开始的顺序,实际过程中是交叉进行的,加载过程中可能就已经开始验证了。
类加载的时机
首先要知道什么时候类需要被加载,Java虚拟机规范并没有约束这一点,但是却规定了类必须进行初始化的5种情况,很显然加载、验证、准备得在初始化之前,下面具体来说说这5种情况:

类加载时机
其中情况1中的4条字节码指令在Java里最常见的场景是:
1 . new一个对象时
2 . set或者get一个类的静态字段(除去那种被final修饰放入常量池的静态字段)
3 . 调用一个类的静态方法
类加载的过程
下面我们一步一步分析类加载的每个过程
1. 加载
加载是整个类加载过程的第一步,如果需要创建类或者接口,就需要现在Java虚拟机方法区创建于虚拟机实现规定相匹配的内部表示。一般来说类的创建是由另一个类或者接口触发的,它通过自己的运行时常量池引用到了需要创建的类,也可能是由于调用了Java核心类库中的某些方法,譬如反射等。
一般来说加载分为以下几步:
通过一个类的全限定名获取此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
创建名字为C的类,如果C不是数组类型,那么它就可以通过类加载器加载C的二进制表示(即Class文件)。如果是数组,则是通过Java虚拟机创建,虚拟机递归地采用上面提到的加载过程不断加载数组的组件。
Java虚拟机支持两种类加载器:
引导类加载器(Bootstrap ClassLoader)
用户自定义类加载器(User-Defined Class Loader)
用户自定义的类加载器应该是抽象类ClassLoader的某个子类的实例。应用程序使用用户自定义的类加载器是为了扩展Java虚拟机的功能,支持动态加载并创建类。比如,在加载的第一个步骤中,获取二进制字节流,通过自定义类加载器,我们可以从网络下载、动态产生或者从一个加密文件中提取类的信息。
关于类加载器,会新开一篇文章描述。
2. 验证
验证作为链接的第一步,用于确保类或接口的二进制表示结构上是正确的,从而确保字节流包含的信息对虚拟机来说是安全的。Java虚拟机规范中关于验证阶段的规则也是在不断增加的,但大体上会完成下面4个验证动作。

验证
1 . 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。
主要验证点:
是否以魔数
0xCAFEBABE开头主次版本号是否在当前虚拟机处理范围之内
常量池的常量是否有不被支持的类型 (检查常量tag标志)
指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据
Class文件中各个部分及文件本身是否有被删除的或者附加的其他信息
...
实际上验证的不仅仅是这些,关于Class文件格式可以参考我的深入理解JVM类文件格式,这阶段的验证是基于二进制字节流的,只有通过文件格式验证后,字节流才会进入内存的方法区中进行存储。
2 . 元数据验证:主要对字节码描述的信息进行语义分析,以保证其提供的信息符合Java语言规范的要求。
主要验证点:
该类是否有父类(只有Object对象没有父类,其余都有)
该类是否继承了不允许被继承的类(被final修饰的类)
如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,出现不符合规则的方法重载,例如方法参数都一致,但是返回值类型却不同)
...
3 . 字节码验证:主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,字节码验证将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
主要有:
保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似的情况:操作数栈里的一个int数据,但是使用时却当做long类型加载到本地变量中
保证跳转不会跳到方法体以外的字节码指令上
保证方法体内的类型转换是合法的。例如子类赋值给父类是合法的,但是父类赋值给子类或者其它毫无继承关系的类型,则是不合法的。
符号引用验证:最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段解析阶段发生。符号引用是对类自身以外(常量池中的各种符号引用)的信息进行匹配校验。
通常有:
符号引用中通过字符串描述的全限定名是否找到对应的类
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方
原创不易,完成人机校验,阅读全文