一、JVM类加载机制流程如下
二、逐步说明各步骤的主要工作
1.JVM加载(读取)二进制字节流到主内存,可从多个途径读取流信息。常见如使用javac HelloWorld.java获得的HelloWorld.class文件、或程序编译后存储在数据库,之后运行时读取、或"牛x"手写class文件内容、Java命令打jar包、mvn打包war等,总而言之,JVM对内容的存储形式不做特别要求,因为不论什么文件类型,最终都会对二进制流内容作校验。加载阶段完成时,字节流存储在方法区中,同时实例化一个java.lang.Class对象作为应用程序访问方法区这个类型的外部接口,如Class aKlass = a.getClass(); aKlass.getName()等。
2.内容验证阶段
2.1 文件格式验证
所谓文件格式验证是指JVM支持的文件格式,JVM通过是否包含特定的内容信息进行验证。
1.魔数验证:字节流以0xCAFEBABE开头
2.版本号验证。当前虚拟机是否能够处理当前版本,比如用低版本虚拟机加载高版本虚拟机编译后的class文件,会报错。
3.常量池验证。常量池数量,常量池内容等
4.整个文件结构是否完整。Java字节码文件是内容连续的、有序的、格式固定的,因此有任何添加或删除则都会被视为非法
5.其他验证如文件名、方法名长度、编码等
文件格式验证通过后,字节流才会被转换为一个Java类型存储在方法区,接下来的验证则是基于方法区中的存储结构进行的
2.2 元数据验证
1.父类验证(除java.lang.Object外,其他类都应该有父类)
2.继承关系(包括Java语义中的继承与实现)验证。比如是否继承了不能继承的类,是否继承了多个具体类,是否实现(implements)了类而非接口等
3.是否满足类定义验证。比如非抽象类是否完全实现了父接口的所有方法
4.是否覆盖父类中不允许继承的方法,本类中是否发生错误的重载等
...
该阶段主要是对内容进行语义分析,确保符合Java语言规范要求
2.3 字节码验证
简单点说就是对代码中的方法做校验,确保方法在运行时不会危害到自身。
1.方法中的对象赋值时,类型检查。是否将C类型赋值给其他非C以及非C父类类型,如此将导致类型不匹配异常(受检异常)
2.操作数栈的类型与指令代码序列一致
...
2.4 符号引用验证
1.该引用指向的类是否存在
2.该引用指向的方法,在特定类中是否存在
3.该引用指向的字段,在特定类中是否存在
4.该引用指向的类、方法、字段是否具备访问权限
...
主要验证符号引用对应的信息是否能够匹配
3.准备阶段,经过第2阶段,认为读入的字节流数据信息合法,且加载阶段已初始化的类型可用。此时会对属于该类型的变量(类变量、类方法)进行内存分配与零值初始化(Integer零值为null, int 零值为0等)。
1.类变量所使用的内存将在方法区分配
2.注意被static final修饰的变量会在此阶段直接分配真实值(你在代码中写的值如static final int a = 3,此时会将a直接赋值为3,并存储在fields字段表属性attribute的ConstantValue属性中)
4.解析阶段,该阶段为符号引用替换为直接引用的过程。涉及到类或接口、字段、类方法、方法类型、方法具柄和调用点7类。
举个例子比较好理解:
public static String say(){
return "hello";
}
准备阶段已经对say方法做了内存初始化,比如内存地址是0xabcd1234,则在此阶段将符号引用say转换为具体的内存地址入口,该内存地址就是直接引用。
5.初始化阶段,执行类构造器<clinit>()方法。编译器自动将静态块、静态变量收集合并产生类构造器方法(初始化顺序依赖源文件中的顺序)
1.<clinit>()方法确定会在子类的构造器方法之前执行,无需显示调用父类的构造器方法
2.java.lang.Object的类构造器方法是最先执行的
3.父类的静态语句、静态块一定优先子类初始化
4.类构造器方法仅执行一次,并由虚拟机保证其多线程环境中被正确的加锁与同步
6.使用与卸载