JAVA虚拟机的类加载机制

猪小花1号2018-09-11 09:21

作者:吕宗胜


在JAVA中,有一句口号我们众所周知,“一次编写,到处运行”。而支持JAVA这种特性的关键之处在于JAVA虚拟机和字节码存储格式,JAVA虚拟机不与JAVA变成语言捆绑,只与Class文件所关联。正因为JAVA只与Class文件相关,它不关心Class的来源,所以虚拟机对Class文件的格式有着十分严格的定义,以便进行Class的合法性等的验证。而Class文件需要被JAVA虚拟机加载,才能最终被虚拟机执行。

1.类的生命周期

类从被加载到虚拟机到卸载出内存,它的整个生命周期有:加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备和解析这3个部分统称为连接。

从类加载的原则上来说,类的生命周期一般是按照这个顺序来的,这里我们重点来看看加载、验证、准备、解析和初始化这5个过程,这5个过程是类在使用之前会完成。下面我们重点来介绍一下类不同生命周期的具体过程。

1.1加载

在加载阶段,虚拟机要完成一下3件事情:

a. 获取类定义的二进制流(通过类的全限定名来获取)

b. 将该二进制流所代表的静态存储结构转化为方法区的运行时数据结构

c. 在内存中生成一个代表该类的java.lang.Class对象。

1.2 验证

验证是连接阶段的第一步,这一阶段的目的主要是为了验证Class文件的正确性,JAVA虚拟机对Class文件的来源并没有要求,甚至我们可以用文本编辑器来编写Class文件,所以验证是非常有必要的,验证是否严谨,直接关系虚拟机最终运行的正确性。

验证阶段主要包括对Class文件格式的验证、元数据的验证、字节码的验证以及符号引用的验证。

1.3 准备

准备阶段是开始为类变量进行内存分配并设置变量的初始值。这里说明一下类变量是指被static修饰的变量,而不包含实例变量。这里的类变量的初始化时在方法区中进行内存分配,而不是在堆中进行的。这里说的变量的初始值,是指设置成零值。

public static int value=100; //在类变量初始化的阶段,value的值为0,而不是100

当然,变量被设置成零值,也不是绝对的,如果该类变量是常量的话,则会直接设置成常量值。

1.4 解析

解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程。

1.5 初始化

类的初始化是类加载过程的最后一步,在准备阶段,类变量已经进行了初始化,而在初始化阶段,虚拟机则会根据程序来初始化类变量和其他资源。

2.类加载器

类加载器是用于实现类的加载动作,在JAVA中,类的唯一性是由类和加载该类的加载器一同唯一决定的。每一个类加载器,都拥有一个独立的类名称空间。所以简单的来说,如果两个类相等,只有在这两个类是由同一个类加载器加载的前提下有成立。

2.1 双亲委派模型

从JAVA虚拟机的角度来说,存在两种不同的类加载器:启动类加载器和其他类加载器。启动类加载器是由C++来实现的,是虚拟机的一部分;而其他类加载器则有JAVA来实现,独立于虚拟机,且继承自java.lang.ClassLoader。

从开发者的角度来看,JAVA程序一般使用下面3中类加载器:启动类加载器,扩展类加载器、应用程序类加载器。

启动类加载器(Bootstrap ClassLoader):该类负责加载\lib目录中的,并且被虚拟机识别的类库。

扩展类加载器(Extension ClassLoader):该类负责加载\lib\ext目录中的类库

应用程序类加载器(Application ClassLoader):该类负责加载用户类路径上所指定的类库

我们的应用程序一般都是由这3种类加载器加载的,当然,如果有必要,我们也可以加入自己定义的类加载器。而这3种类的层次关系我们称为双亲委派模型。该模型除了顶层的启动类加载器外,其余的类加载器都有自己的父类加载器。

双亲委派模型的工作过程是:当一个类加载器收到了类加载请求的时候,它首先会委派给父类加载器去完成,而每一个加载器都会如此执行,只有当父类加载器无法完成类加载的时候,子加载器才会尝试自己去加载。使用双亲委派模型的一个好处是Java类会随着类加载机制具备了一种优先级的层级关系。


本文来自网易实践者社区,经作者吕宗胜授权发布