虚拟机类加载机制

类加载机制

​ 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类加载的时机

​ 类从加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析3部分统称为连接。

图1

​ 虚拟机规范有且只有以下5种情况,必须对类进行"初始化"(相应的加载、验证、准备自然也需要在此之前开始):

  • 遇到new、getstatic、putstatic或invokestatic这四天字节码指令时,如果类没有进行过初始化,则需要先触发初始化。

    这四条指令的Java代码场景:使用new实例化对象、读取或设置一个类的静态字段以及调用一个类的静态方法。

  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发初始化。

  • 当初始化一个类的时候,如果发现其父类没有进行过初始化,需要先触发父类的初始化。

  • 虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个主类。

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

类的加载过程

加载

​ 类加载机制的第一个阶段,虚拟机完成的三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转换为方法区运行时数据结构;
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。

对于获取二进制流这条,并没有指明是哪里获取、怎样获取。所以读取方式就有很多:

  • 从Class文件读取
  • 从ZIP包中读取。(jar包、war包)
  • 从网络中获取。
  • 运行时计算生成。

等等

​ 加载完成之后,虚拟机外的二进制字节流就按照虚拟机所规定的格式存储在方法区中,方法区中数据的存储格式由虚拟机实现定义,虚拟机规范没有规定此区域的具体数据结构。然后在内存中实例化一个java.lang.Class类对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。

加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段有可能以及开始。但是这些夹杂在加载阶段进行的动作,仍然属于连接阶段的内容,这两个阶段的开始事件仍然有固定的先后顺序。

验证

​ 确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段的严谨与否直接决定了Java虚拟机能否承受恶意代码的攻击。验证阶段的工作量在虚拟机的类加载子系统中占了相当大的一部分。

​ 验证阶段主要完成下面四个部分的验证:

文件格式验证

​ 该阶段的主要目的是保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求。

主要包括:

  • 魔数开头是否是0xCAFEBABE
  • 主次版本号是否在当前虚拟机范围内
  • 常量池的常量中是否有不被支持的常量类型

。。。

元数据验证

​ 第二阶段的只有目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。

主要包括:

  • 此类是否存在父类(除Object类之外,所有的类都应该有父类)
  • 此类的父类是否继承了不允许被继承的类(被final修饰)
  • 类中的字段、方法是否域父类产生矛盾

。。。

字节码验证

​ 主要目的是通过数据流和控制流分型,确定程序语义是合法的、符合逻辑的。在第二个阶段完成之后,这个阶段将对类的方法体进行校验分型,保证被校验的类的方法在运行时不会做出危害虚拟机安全的事件。

​ 需要注意的是,如果一个类方法体没有通过字节码验证,那肯定是有问题的;如果一个方法体通过了字节码验证,也不能说明它一定是安全的。 通过程序区建议程序逻辑是无法做到准确的。

符号引用验证

​ 符合引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,那么将抛出异常java.lang.IncompatibleClassChangeError的子类,如java.lang.NoSuchMethodError、java.lang.NoSuchFieldError等。

准备

​ 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所应用的内存都将在方法区中进行分配。

​ 这里进行内存分配的仅包含内变量(static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

​ 还有,此处所说的初始值"通常情况"下时数据类型的零值,假定

public static int value = 123;

,那么在准备阶段过后,变量value的值是0,而不是123.因为这个时候尚未开始执行任何Java方法,而把123赋给value的putstatic指令是程序被编译后,存放于类构造器()方法只中,所有这个赋值动作会在初始化阶段才会执行。

​ 但是如果类字段的字段属性表中存在ConstantValue属性那么在准阶段就会被初始化为ConstantValue属性所指定的值,例如:

public static final int value = 123;

解析

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

  • 常量池:指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
  • 符号引用:符号引用以一组符号来描述所引用的目标,符合可以是任何形式的字面量(常量池中存储的那些描述类、方法、接口的字面量),只要在使用的时候可以无歧义的定位到目标即可。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

​ 所以解析过程可以解读为:虚拟机将运行时常量池中那些仅代表其他信息的符号引用解析为直接指向所需信息所在地址的指针。

解析阶段主要有以下动作:

  • 类或接口的解析(对应常量池的CONSTANT_Class_info)
  • 字段解析(对应常量池的CONSTANT_Fieldref_info)
  • 类方法解析(对应常量池的CONSTANT_Methodref_info)
  • 接口方法解析(对应常量池的CONSTANT_InterfaceMethodref_info)

动态链接

​ 大部分JVM的实现都是延迟加载或者叫做动态连接。JVM装载某个类A时,如果类A中有引用其他类B,虚拟机并不会将这个类B也同时装载进JVM内存,而是等到执行的时候才去装载。而这个被引用的B类在引用它的类A中的表现形式主要被登记在了符号表中,而解析的过程就是当需要用到被引用类B的时候,将被引用类B在引用类A的符号引用名改为内存里的直接引用。

初始化

​ 准备阶段,变量已经赋值过一次系统要求的初始值(零值),而在初始化阶段,会依据开发人员的计划去初始化类的变量和其他资源,也可以理解为:初始化阶段是在执行类构造器()方法的过程。

​ ()方法与类的构造函数(或者说实例构造器()方法)不同,它不需要显示的调用父类构造器,虚拟机会保证在子类的()方法执行之前,父类的()方法已经执行完毕。因此在虚拟机中第一个被执行的()方法的类肯定是java.lang.Object.

类加载器

​ 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。也就是说,如果要比较两个类是否相等,只有在这两个类是同一个类加载器加载的前提下才有意义。

双亲委派模型

​ 从Java虚拟机角度来看,只存在两种类加载器:

  • 启动类加载器:属于虚拟机的一部分
  • 其他类加载器:全部继承自抽象类java.lang.ClassLoader

​ 从开发人员角度来看,大多数Java程序都会用到以下3中类加载器:

  • 启动类加载器

  • 扩展类加载器

  • 应用程序类加载器

    应用程序由这三种类加载器相互配合进行加载,如果有必要可以使用自定义的类加载器。

图2

​ 上图为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。此处的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码。

​ 工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载器最终都应该传送到顶层的启动类加载器中,只有父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类),子加载器才会尝试自己加载。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值