本文内容:主要讲述一下类加载的生命周期和双亲委派模型
以前也写过类的加载机制,今天借助《深入理解虚拟机》再补充一下下
类加载的生命周期
总共分为:加载、验证、准备、解析、初始化、使用、卸载
如下
加载
加载是类加载的过程之一,在加载阶段,虚拟机需要完成以下3件事情
- 通过一个类的全限定名类获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
数组的加载有一些不同:
数组类本身不通过类加载器创建,而是由Java虚拟机直接创建。但是数组类的元素类型最终要靠类加载器去创建,遵循如下规则
- 如果元素类型是引用类型,那就使用双亲委派模型加载这个类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识,保证与类加载器确定唯一性
- 元素类型是基本类型,Java虚拟机将会把数组C标记为与引导类加载器相关联
- 数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那么数组类得到可见性将默认为public
验证
验证共有四个阶段,分为文件格式验证、元数据验证、字节码验证和符号引用验证
文件格式验证-第一阶段
验证字节流是否符合Class文件的规范,该验证的主要目的是保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求。
部分验证内容:
- 是否以魔数0xCAFEBABE开头
- 主、次版本号是否在当前虚拟机处理范围之内
- 常量池中常量中是否有不被支持的常量类型
- …
元数据验证-第二阶段
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,这个阶段可能包括的验证点如下:
- 这个类是否有父类(除了java.lang.Object)
- 这个类的父类是否继承了不允许被继承的类
- 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
- 类中的字段、方法是否与父类产生矛盾
字节码验证-第三阶段
主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段将对类的方法体进行检验分析,保证被校验类的方法在运行时不会出错。
符号引用验证-第四阶段
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段-解析阶段中发生。
符号引用验证可以看做是对类自身以外的信息进行匹配性校验,如下:
- 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
- 符号引用中的类、字段、方法的访问性是否可以被当前类访问
- …
符号引用验证的目的是确保解析动作能正常执行
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配。
注意:
- 此时进行内存分配只是被static修饰的成员变量,不包括实例变量(在对象初始化时随着对象分配在java堆中)
- 初始值指的是该数据类型的零值,对于引用类型为null、boolean为false、int为0等;只有final修饰的,才会被初始化为指定的值;无final修饰,赋为指定的值则是在初始化过程中进行。
解析
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
- 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时无歧义的定位到目标即可。
- 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
虚拟机规范并未规定解析阶段发生的时间。
虚拟机实现可以对第一次解析的结果进行缓存从而避免解析动作重复进行。
初始化
初始化阶段才是真正开始执行类中定义的Java程序代码(或者说是字节码)
还有根据程序员通过程序制定的主观计划去初始化变量和其它资源,这块才是真正的初始化。
类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的命名空间。
一般来说是三种
- Bootstrap ClassLoader:启动类加载器,一个类由启动类加载器加载,返回该类的的类加载器为null,负责加载<JAVA_HOME>\lib目录中的类
- Extension ClassLoader:扩展类加载器,负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
- Application ClassLoader:应用程序类加载器,也可以称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用,一般情况下也是程序默认的类加载器。
java的应用程序就是由这三种类加载器互相配合加载的。
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先自己不会去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。如果最终这个类无法被加载
双亲委派模型的优点:
- 安全
- 避免类的重复加载