深入理解JVM(二)——类加载过程与类加载器双亲委派模型

1.类加载机制概述

2.类加载的过程

3.类加载器

4.双亲委派模型

1.类加载机制概述
上一篇文章:深入理解JVM(一)——JVM简介和运行时数据区结构
主要讲解了JVM的组成部分以及运行时数据区的组成部分,这节我们来讲一下类加载子系统。
深入理解JVM(二)——类加载过程与类加载器双亲委派模型_第1张图片
所谓的类加载机制:
就是虚拟机把描述类的数据从class加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的JAVA类型,这就是虚拟机的加载机制。

2.类加载的过程
类从硬盘加载到内存,到卸载出内存,整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载。(加验准解初使卸)
验证,准备,解析3个部分统称为链接。
深入理解JVM(二)——类加载过程与类加载器双亲委派模型_第2张图片
接下来我们将对以上步骤挨个进行分析:

2.1加载
加载,是类加载的第一个阶段,希望读者不会混淆这两个很相似的词语。在加载阶段,虚拟机要完成以下三个事情:
1) 通过一个全限定名(包名+类名)来获取定义此类的二进制字节流。
2) 将这个字节流的字段信息转化为方法区的运行时数据结构。
3) 在内存中是生成一个此类的对象,作为方法区这个类的各种数据的访问入口。

简单地来说,就是通过包名+类名把类加载到内存中,然后在方法区中生成该类的模板,供类在新建对象的时候使用。

2.2验证
验证是连接的第一步,这一阶段的目的是为了确保Class文件字节流的内容符合虚拟机规范,并且不会危害虚拟机自身安全。
JAVA本身是一门相对安全的语言,无法做到访问数据边界之外的数据,跳转到不存在的代码之类的事情,但是上述JAVA代码无法做到的事情,字节码层面却都是可以做到的,所以虚拟机如果不检查输入的字节流,很可能因为载入了有害的字节流导致系统的崩溃。所以验证是十分重要的,这个阶段是否严谨,直接决定了JAVA虚拟机是否能承受恶意的代码的攻击。
从整体上看,验证阶段大致会完成下面4个阶段的检查动作:文件格式验证,元数据验证,字节码验证,符号引用验证。
1)文件格式验证
第一阶段要验证字节流是否符合class规范,举例如下:
是否以魔数0xCAFEBABE开头
主次版本号是否在当前虚拟机处理范围之内
常量池中的常量是否有不被支持的常量类型
等等
该阶段主要目的确保输入的字节流能正确地解析并存储于方法区之内,只有通过了这个阶段的验证后,字节流才会进入内存你的方法区中进行存储,所以后面的3个验证阶段全部都是基于方法区的存储结构进行的,不会操作字节流。

2)元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求:
这个类是否有父类
这个类的父类是否继承了不允许被继承的类
如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
类中的字段,方法是否与父类产生矛盾
主要对类的元数据信息进行语义校验,保证不存在不符合java语言规范的元数据信息。

3)字节码验证
最复杂的一个阶段,主要目的通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。
保证任意时刻操作数栈的数据类型与指令代码序列都能配合出现工作,不会出现类似:在操作栈防止了一个int类型的数据,使用时却按long类型来加载入本地变量表中
保证跳转指令不会跳转到方法体以外的字节码指令上

4)符号引用验证
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在链接的第三个阶段-解析阶段发生。可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验以下内容:
符号引用通过字符串描述的全限定名是否能找到对应的类
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
符号引用中的类,字段,方法的访问性是否可以被当前类访问

2.3准备
准备阶段是正式为变量分配内存并设置变量初始值的阶段,这些变量所使用的的内存都将在方法区内进行分配。
这个时候进行内存分配的仅仅包含类变量(被static修饰的变量),而不包括实例变量。

2.4解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
解释一下这两种引用的概念。
符号引用:符号引用以一组符号来描述引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。例如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language来代替。

直接引用:直接引用就是可以直接指向目标的指针。就是该对象已经被分配了内存地址,并且已经指向该内存地址。

2.5初始化
类的初始化是类加载过程中的最后一步,前面的过程中,除了加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码。

关于初始化,我们都看过一张图,我相信大家对于这张图已经很了解了。
深入理解JVM(二)——类加载过程与类加载器双亲委派模型_第3张图片

3.类加载器
接下来我们介绍类加载器,在之前“加载”这个阶段的时候我们说过,虚拟机通过全限定名来获取二进制字节流,实现这个动作的就是类加载器。

类加载器一般有四种
启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器。
深入理解JVM(二)——类加载过程与类加载器双亲委派模型_第4张图片

4.双亲委派模型
在了解每一个类加载器之前,我们先了解一下双亲委派模型
双亲委派模型:
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因为所有的类加载器请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己加载。

引导类加载器,主要加载rt.jar里面的类文件
image.png
我们熟悉的String,Object等,等各种jdk自带的类型,都存在于rt.jar中,这些类都通过引导类加载器加载。

扩展类加载器,主要加载ext包下的类,比如通过maven,引入的包里面的类,都是通过扩展类加载器加载的。
image.png

应用程序类加载器,主要加载自己写的类。

使用双亲委派模型有一个显而易见的好处就是java的类随着它的类加载器一起具备了一种带有优先级的层次关系,例如Object,它位于rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序中的各种类加载器环境中都是同一个类,相反,如果没有使用双亲委派模型,由于各个类加载器自行加载的话,如果用户自己编写了一个Object类,那么就会出现多个Object,程序也会一片混乱。

总结:
今天我们介绍了类加载的加载过程,分为加载,验证,准备,解析,初始化,使用,卸载,并且大致介绍了每一个步骤里面的过程,还介绍了双亲委派模型,以及双亲委派模型的工作过程。

你可能感兴趣的:(深入理解JVM(二)——类加载过程与类加载器双亲委派模型)