java类加载机制

一、类加载过程概述

虚拟机描述类的数据从Class文件(不止是文件系统上的class文件,而是一串二进制的字节流,以任何形式存在都可以)加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

 

类从被加载到虚拟机内存,到卸载出内存,总共有以下7个阶段:

1. 加载 2.验证 3. 准备 4. 解析 5. 初始化 6.使用 7.卸载

其中2、3、4三个阶段称为连接阶段,所以又可以称为5个阶段:

加载>连接>初始化>使用>卸载

 

Java虚拟机规定一个类加载的固定顺序有 加载>验证>准备>初始化>卸载 这5个阶段,但是对解析没有规定强制的顺序,即解析即可在初始化之前,也可在初始化之后,这是为了支持Java的运行时绑定,又称为动态绑定或者晚期绑定

 

Java虚拟机对初始化的时机做了强制约束,什么时候会对类进行初始化?

有5个时机会强制对类立即进行初始化

1. 实例化对象(new关键字)、设置、读取静态变量、调用静态方法

2. 使用java.lang.reflect包对类进行反射调用时

3. 初始化一个类,会首先对其父类进行初始化

4. 虚拟机启动时,要执行的主类(包含main方法的那个类)

5. 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对所应的类没有进行初始化,需要首先触发其初始化

 

以上5中情况被称为主动引用

除此之外,所有引用类的方式都不会触发初始化,这被称为被动引用

类和接口在这里有所不同,当一个接口被初始化时,不会触发所继承接口的初始化,只有在使用父接口的时候(如引用接口中定义的常量)才会对父接口进行初始化。

 

被动引用有三个例子:

1. 子类调用父类的静态变量,不会对子类进行初始化,而只是对父类进行初始化

2. 通过数组的方式实例化一个类,不会对该类进行初始化

3. 调用一个类中定义的常量,不会对该类进行初始化,因为在编译时,这个常量已经被编译到调用类的常量池中,和被调用类再无联系

 

二、类加载机制阶段

上面概述了类加载机制的定义,我们也知道类加载分为7个阶段,下面具体列出每个阶段的功能以及作用

加载

     加载阶段就是指把class文件读入内存,并为之创建Class对象,所以,系统中所使用的每一个类,都会有一个Class对象。

     类的加载由类加载器完成,类加载器通常有JVM提供,JVM提供的类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader父类来自定义类加载器。

     通过使用不同的类加载器,可以从不同来源加载类的二进制数据,如磁盘文件、JAR/WAR包、网络、动态编译java源文件等。

     类加载器通常无需等到“首次使用”类的时候再开始加载,Java虚拟机规范允许系统预先加载某些类

链接

 上面说过,链接阶段是验证、准备、解析这三个阶段的总称

验证

    通过上面的加载阶段,JVM已经把描述类的class字节流读入到内存,那么这些数据是否是java认识的程序呢、以及是否符合Java执行的标准,危害系统安全等,都需要通过验证这一阶段进行认证。这一阶段也是JVM加载类的过程中最复杂的一部分

    这一阶段分为4个验证步骤:

1. 文件格式验证:验证字节流是否符合Class文件的规范,并且能够被当前版本的虚拟机加载处理:例如,是否以魔数开头(0xCAFEBEBE)、主次版本号是否在当前JVM版本支持范围内、常量池中是否有不被支持的类型。指向常量中的索引值是否存在不存在的常量或者不符合类型的常量。经过了这一阶段,Class字节码将会被载入方法区中进行存储,后续的操作都是直接读取方法区的存储结构执行的。

2. 元数据验证:对字节码进行语义分析,是否符合Java的语言规范

3. 字节码验证:最重要的验证环节,分析数据流和控制,确认语义是符合逻辑的。主要是针对元数据验证后的方法体的验证,保证类方法在运行时不会有危害出现。

4. 符号引用验证:主要针对的是符号引用替换为直接引用的时候,确定访问类型等涉及到引用的情况,保证引用一定可以访问到,不会出现类无法访问等情况。

 

准备

  准备阶段负责为类的静态变量分配内存,并设置初始值,如int类型值=0等

解析

  将类的二进制数据中的符号引用替换为直接引用,符号引用只是引用的字面量,而直接引用就是指向该目标的指针、偏移量、或者能够直接定位的句柄了。直接引用是和内存中的布局有关的。

 

 

初始化

    初始化是为类的静态变量赋予正确的值的时候,还记得上面说的准备阶段,是为类的静态变量分配内存,并分配初始值,如

private static int a = 123; 在准备阶段已经为其分配好内存,初始值是0,到初始化阶段,才开始将这个静态变量的值赋值为123。原因是类的初始化阶段,JVM会调用<clinit>内部方法,这个方法中包含了所有静态变量赋值的语句。

 

三、类加载器

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机的外部来实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块被称为“类加载器”

    对于任意一个类,都是由类的全限定名+类加载器 作为类的唯一标识, 换句话说,如果一个类被不同的两个类加载器加载的,那么这两个类在虚拟机看来也不是同一个类。在使用Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法时都会返回false,或者用instanceof关键字做对象的关系判定等情况。

双亲委派模型

在介绍双亲委派模型之前,先看一下类加载器的分类:

 

类加载器的分类

从java虚拟机的角度,有两种类加载器,由C++实现的启动类加载器,和所有其它的类加载器(Java实现且继承于抽象父类ClassLoader)

从开发人员的角度,类加载器可以划分的更细致一些:

1. 启动类加载器 (Bootstrap ClassLoader),有时也叫引导类加载器

     负责将存放在%JAVA_HOME%/lib 目录中、或被-Xbootstrappath 参数所指定的路径中,且是虚拟机识别的类库(按照名称识别,如rt.jar,所以如果名称不符,即使放在上面的路径里也不会被启动类加载器加载)加载到虚拟机内存中

 

2. 扩展类加载器 (Extension ClassLoader)

    这个加载器负责加载%JAVA_HOME%/lib/ext 目录中,或者被java.ext.dirs系统变量所指定的路径中所有类库。开发者也可以直接使用扩展类加载器

3. 应用程序类加载器 (Application ClassLoader)

    这个类是ClassLoader中getSystemClassLoader() 方法的返回值,所以又叫系统类加载器,它负责加载用户类路径(ClassPath)上所指定的类库,

 

由此可见,所谓不同的类加载器,本质上是为了加载不同地方的类而设计的,如果没有这么多的类加载器,怎么能够完成磁盘上甚至网络上的字节码class文件呢。

 

类加载器的双亲委派模型(Parents Delegation Model)

那下面来说说双亲委派模型,首先看一个经典的双亲委派模型图

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

这里类加载器的父子关系一般不会以继承(Inheritance)的关系来实现,而是以组合(Composition)关系来复用父类加载器的代码。

双亲委派模型是一种推荐的类加载器实现方式,而并非强制

其工作过程是:如果一个类加载器收到了加载一个类的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父类加载器去完成,每一个层级都是如此,只有当父加载器反馈自己无法完成这个加载请求(无法搜索到这个类)

时,子加载器才会尝试自己去加载。

由上面的描述可见,这里的双亲委派模型,其实就是父类加载器委派模型,双亲这个词刚开始会让我有所误解。

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值