1、类加载时机
类在虚拟机的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段,其中验证、准备、解析3个部分统称连接(Linking)。
以下5种情况需要立即对类进行初始化:
(1) 遇到new、getstatic、putstatic、invokestatic这4条字节码指令
(2) 使用java.lang.reflect包的方法对类进行反射调用的时候
(3) 初始化类的时,发现父类没有初始化,需要先触发其父类的初始化
(4) 虚拟机启动时,会先初始化执行的主类(main()所在类)
(5) 使用动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄,且方法句柄对应的类没有初始化,需要先触发其初始化
2、类加载过程
2.1 加载
在加载阶段,虚拟机需要完成3件事:
(1) 通过类的全限定名来获取此类的二进制字节流
(2) 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3) 在内存区生成代表这个类的java.lang.Class对象,作为方法区该类各种数据的访问入口
2.2 验证
验证的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身的安全。验证分4个阶段:
(1) 文件格式验证:是否以魔数0xCAFEBABE开头、主次版本是否在当前虚拟机处理范围、常量池中的常量是否存在不被支持的类型、指向常量的索引值是否有指向不存在的常量或类型等
(2) 元数据验证:是否有父类(Object除外)、是否继承了final修饰的类、非抽象类是否实现了父类或接口中要求实现的所有方法等
(3) 字节码验证:针对类的方法体,保证任意时刻操作数栈的数据类型与指令代码序列能配合工作、保证跳转指令不会跳转到方法体之外、保证方法体内的类型转换有效等
(4) 符号引用验证:符号引用中通过字符串描述的全限定名是否能找到对应的类、指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段、符号引用中的类、字段、方法的访问性是否可以被当前类访问等
2.3 准备
准备阶段是为类变量(static)分配内存并设置初始值(通常情况下是零值,final修饰除外),所用内存将在方法区分配,基本类型零值如下:
2.4 解析
解析阶段是将常量池中的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量。
直接引用:可以是直接指向目标的指针,相对偏移量、或能间接定位到目标的句柄
解析动作主要针对类、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用。
2.5 初始化
初始化阶段,才真正开始执行类中定义的java程序代码,是执行类构造器
3、类加载器
通过一个类的全限定名来获取描述此类的二进制流这个动作放到jvm外部实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为类加载器。
3.1 双亲委派模型
站在java虚拟机的角度上讲,只存在两种不同的类加载器:
(1) 启动类加载器(Bootstrap ClassLoader):由c++实现,是虚拟机自身的一部分
(2) 其他类加载器:由java实现,独立于虚拟机外部,全继承于抽象类java.lang.ClassLoader
站在java开发人员的角度上讲,分为三种:
(1) 启动类加载器(Bootstrap ClassLoader):负责将存放在
(2) 扩展类加载器(Extension ClassLoader):由sun.misc.Launcher$ExtClassLoader来实现。主要加载
(3) 应用程序类加载器(Application ClassLoader):由sun.misc.Launcher$AppClassLoader来实现。由于这个类加载器是ClassLoader中getSystemClassLoader()方法返回值,所以也叫系统类加载器。负责加载用户类路径上(ClassPath)指定的类库,是开发过程中默认的加载器,开发者可直接使用。
双亲委派模型:描述类加载器之间的层次关系的就是双亲委派模型。
双亲委派模型要求除顶层的启动类加载器外,其余类加载器都应有自己的父类加载器。
加载器之间的父子关系不是靠继承的关系实现,是使用组合关系的关系来复用父类加载器的代码。
双亲委派模型工作过程:若一个类加载器收到类加载请求,他首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一层类加载器都是如此,所以所有请求都会传送到顶层的BootStrap ClassLoader,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围没找到),子加载器才会自己尝试去加载。
java类以及它的类加载器一起具备了一种带有优先级的层次关系。如 java.lang.Object(它在rt.jar之中)。同一个类 被不同的 类加载器 加载成两个类之后,这两个类是不同的。如果java.lang.Object类被不同的类加载器加载很多次,jvm中存在了多个不同的Object类,那么java类型体系中最基础的行为也无从保证,应用程序会一片混乱。相反,使用了双亲委派模型,无论哪个类加载器去加载,都会委派到BoorStrap ClassLoader进行加载,保证了Object在各个类加载器环境中,都是同一个类。