概述:
在前几篇中我们了解了class文件存储格式的具体细节,而class文件(字节流文件)最终都会加载到虚拟机之后才能被运行和使用,而虚拟机是如何加载这些class类的,加载完之后class文件又会发生什么样的变化?这些就是本篇的内容。从编译原理的角度来说就是java原文件编译阶段完成后如何运行。
虚拟机把class文件加载到内存,并对数据进行 校验、转换解析 和初始化,最终形成可以被虚拟机直接使用的java类型,这些就是java虚拟机的类加载机制。
1.1 类加载的时机:
类从加载到虚拟机开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载。其中:验证、准备、解析阶段被称为连接。
过程是这样的:加载、验证、准备、解析、初始化、使用和卸载;或是这样进行:加载、验证、准备、初始化、解析、使用和卸载(这种执行顺序实现了java语言运行时的动态绑定)。
对于java类的初始化过程,一般是规定了有且只有四种情况下进行初始化:
(1)遇到下列四条指令码:new、gestatic、putstatic、invokestatic。
(2)使用java.lang.reflect包的方法对类进行反射调用的时候。
(3)当初始化一个类,发现其父类没有被初始化时。
(4)当虚拟机启动时,用户指定了一个要执行的主类(包含main方法)时。
实例分析:
(1)对于静态字段,只有直接定义这个字段的类才会被初始化,当引用子类的类访问父类的静态字段时,不再被初始化。
1.2 类加载过程详述:
1.2.1 加载(注意与类加载区分开)
1、加载过程概述:加载是指class文件被加载到内存中,这个阶段虚拟机一般完成以下三件事情:
(1)通过一个类的全 限定 名来获取定义此类的二进制字节流;
(2)将这个字节流所代表的静态存储结构转化为方法区运行时的数据结构;
(3)在java堆中生成一个代表这个类的class对象,作为方法区这些数据的访问入口。
2、加载过程详述:
对于(1)通过一个类的全 限定 名来获取定义此类的二进制字节流;仅仅是通过一个 类的全限定名来加载字节流文件,这显得java在加载阶段很是灵活,因为class文件来源是很多的,并不一定是java原文件编译而来;这一灵活性使得很多重要的java技术都发展了出来,也就是说开发人员可以通过建立自己的类加载器去加载class文件,例如:
从zip包中读取,这很常见,最终成为日后的jar、war、ear格式等;
从网络中获取,可以使用java小程序 Applet;
jsp文件格式:java和html代码混合;等等
1.2.2 验证
1、验证目的:这一阶段的目的主要是判定java字节流文件格式的正确性,只有格式正确了,当前虚拟机才能去运行它。验证时如果格式不对,将报出:java,lang.VertifyError错误。一般一次验证会分成下面四个阶段的验证过程:
(1)文件格式验证:(相当于对class文件的语法分析)
例如:是否魔数OXCAFEBABE开头;主次版本号是不是在范围内等。
(2)元数据验证:(相当于对class文件的语义分析)
例如:这个类是否有父类、类的继承关系是否正确等;
(3)字节码验证:(是最复杂的一个阶段,主要工作是进行数据流和控制流分析)。
例如:保证跳转不会跳转到方法体以外的字节码指令上;
(4)符号引用验证:(在解析阶段发生,可以看作是对类自身以外的信息 进行匹配性的校验。)
例如:另一个类(private\public等)可否被本类使用。
1.2.3 准备
准备阶段是正式为类变量分配内存、并设置变量初始值的过程。注意:分配的必须是static类型的变量,而成员变量在堆中进行分配使用。
举例说明:
(1)public static int value = 123;
准备阶段过后value的初始值为0而不是123;(因为把value赋值为123的putstatic指令是程序被编译后进行的,将在初始化阶段执行)
(2)public static final int value = 123;
准备阶段过后value的初始值为123;
1.2.4 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述引用的目标,例如:CONSTANT_Class_info
直接引用:直接引用可以是直接指向目标的指针 、相对偏移量或是一个能简介定位到目标的句柄。
下面介绍四种引用的解析过程:
(1)类或接口的解析
例子:如果当前处理类是D,有一个类或接口是C,,D要调用C,需要将一个从未解析过的符号引用N解析为C的直接引用,那虚拟机的步骤如下:
<1>如果C不是数组类型:使用D加载器去加载这个类C,此时虚拟机会把代表N的全限定名传给D的类加载器;
<2>如果C是数组类型,并且数组的元素类型为对象,也就是N的描述符类似:java.lang.Integer。则解析过程同<1>
如果需要加载的元素就是java,lang.Integer,接着虚拟机产生一个代表此数组维度和元素的数组对象。
(2)字段解析(查资料)
(3)类方法解析
(4)接口方法解析
1.2.5 初始化
到初始化阶段才是真正执行类中定义的java程序代码(字节码).