前言了解类加载的过程,有利于在类初始化时进行一些功能操作;这篇文章完整的解释了类加载的过程;一、类加载简介1、类加载的生命周期从类加载到虚拟机内存开始,直到从内存中卸载,它的整个生命周期分为7个阶段,加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading);验证、准备、分析三部分统称为连接;2、类加载的过程类加载的过程包括加载、验证、准备、解析、初始化五个阶段;五个阶段??中,加载、验证、准备和初始化四个阶段的发生顺序是确定的,但解析阶段不一定,在某些情况下可以在初始化阶段之后开始,这是为了支持JavaRun-语言的时间绑定(也称为动态绑定或后期绑定);还要注意这里的几个阶段是按顺序开始的,不是按顺序或按顺序完成的,因为这些阶段通常是相互混合的,通常在一个阶段的执行过程中会调用或激活另一个阶段;二、类加载过程详解类加载的全过程:加载、验证、准备、解析、初始化五个阶段执行的具体操作1.加载阶段1.1、在这个阶段,虚拟机要做的事情以下三件事:通过一个类的全限定名获取定义这个类的二进制字节流;将这个字节流表示的静态存储结构转换成方法区的运行时数据结构;在内存中生成一个代表该类的java.lang.Class对象,作为该类各种数据在方法区的访问入口;这里的二进制字节流并不一定要从Class文件中获取,只要二进制字节流符合Class文件的规范,就可以认为是Class文件。比如我们可以从jar包中获取,从网络中获取,运行时生成等等。总之,获取一个Class文件的方式有很多种。1.2.这里需要注意的是,通过类的全限定名获取定义该类的二进制字节流的动作是由类加载器完成的。对于非数组类的加载,我们可以通过自定义类加载器来控制加载。不适用于数组类,因为它不是由类加载器创建的,而是由Java虚拟机直接创建的;但是数组类和非数组类也有很多关系,毕竟组成数组的元素都是非数组类(对于一维数组,对于多维数组,可以是递归加载),非数组类的创建需要类加载器来完成。加载创建数组类的过程如下:如果数组的元素类型是引用类型,则递归加载这个元素类型,这个数组会在加载该元素类型的类加载器的类命名空间上标识;如果数组的元素类型不是引用类型(比如int[]数组),Java虚拟机会将该数组标记为与引导类加载器关联;数组类的可见性与元素类型的可见性一致。如果元素类型不是引用类型,则数组的可见性默认为public;2、验证阶段验证的目的是确保Class文件中字节流所包含的信息符合当前虚拟机的要求,不会危及虚拟机本身的安全。不同的虚拟机对类验证的实现可能不同,但一般完成以下四个阶段的验证:文件格式验证、元数据验证、字节码验证、符号引用验证;文件格式验证:验证字节流是否符合Class文件格式的规范,可以被当前版本的虚拟机处理。这个校验的主要目的是确保输入的字节流能够被正确解析并存储到方法区中。该阶段校验完成后,字节流会进入内存的方法区进行存储,接下来的三个校验都是基于方法区的存储结构;元数据验证:对类Verification的元数据信息进行语义校准(其实就是检查类中每个数据类型的语法),确保不存在不符合Java语法规范的元数据信息;字节码验证:这个阶段验证的主要工作是分析数据流和控制流。验证分析类的方法体,确保被验证类的方法在运行时不会执行危害虚拟机安全的行为;symbolreferenceverification:这是最后一个阶段的校验,发生在虚拟机中将symbolicreferences转换为directreferences时(这个转换发生在parsingphase,后面会解释),主要是检查信息的匹配度除了类本身(常量池中的各种符号引用);3.准备阶段准备阶段是正式为类变量分配内存和设置类变量初值的阶段。这些变量使用的内存将分配在方法区中。在这个阶段,我们需要注意两点。首先,此时的内存分配只包括类变量(static修饰的变量),不包括实例变量。对象实例化时,实例变量随对象一起分配到Java堆中,其次,这里所说的初始值通常是数据类型的零值;4.解析阶段解析阶段是虚拟机将常量池中的符号引用转换为直接引用的过程。符号引用和直接引用的区别和联系在Class类文件结构一文中已经比较过,这里不再赘述。如上所述,解析阶段可以在初始化之前开始,也可以在初始化之后开始。虚拟机根据需要判断,是在类被加载器加载时(初始化前)解析常量池中的符号引用,还是等到一个符号引用在使用前解析完毕(初始化后);对同一个符号引用进行多次解析请求是很常见的,虚拟机实现可能会缓存第一次解析的结果(运行时往往会在数量池中记录直接引用,并将常量标记为已解析),从而避免重复解析动作;解析动作主要针对类或接口、字段、类方法、接口方法四种类型的符号引用进行,对应常量池有四种类型的常量:CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info;下面是四个符号的解析过程:(1)类或接口的解析假设当前代码在类D中,符号引用N解析为类或接口C的直接引用,则虚拟机会经过如下过程:如果C不是数组类型,虚拟机会把代表N的全限定名传递给D的类加载器区域来加载这个类。在加载过程中,由于元数据校验和字节码校验的需要,可能会触发其他相关类的加载,比如该类的父类或实现的接口。如果加载过程出现异常,解析过程就会失败;如果C是数组类型,数组的元素类型是对象,则按照第一点加载数组元素类型。然后虚拟机生成一个数组对象,表示数组的维度和元素;如果以上步骤都没有异常,那么C在虚拟机中实际上已经成为一个有效的类或接口,只是在解析完成之前需要进行符号化。参考验证以确认D是否可以访问C;(2)字段解析解析一个未解析的字段符号引用,首先会解析字段表中class_index项索引的CONSTANT_Class_info符号引用,即对该字段所属的类或接口的符号引用。如果在解析此类或接口时出现异常,将导致解析域失败。如果解析成功,则继续解析该字段。具体规则如下:如果类或接口C本身包含简单名称和字段描述符都与目标匹配的字段,则返回对该字段的直接引用并结束搜索;否则,如果该接口是用C实现的,它会根据继承关系从下到上递归搜索每个接口及其父接口。如果接口中包含简单名称和字段描述符都与目标匹配的字段,则返回该字段的直接引用,搜索结束;否则,如果C不是Object,它会根据继承关系从下往上递归查找父类。如果父类中包含简单名称和字段描述符都与目标匹配的字段,则返回该字段的直接引用,搜索结束;否则,搜索失败;之后,将对返回的字段进行权限验证。如果您无权访问该字段,将抛出java.lang.IllegalAccessError异常;(3)类方法分析第一步类方法分析和字段分析同样,也需要先解析出类方法表的class_index项中索引的方法所属的类或接口的符号引用。如果解析成功,则进行如下处理(该类也用C表示):类方法和接口方法符号引用的常量类型定义是分开的。如果在类方法表中发现class_index中索引的C是接口,则直接失败;然后,将在C类中搜索该方法;否则,会在C类的父类中递归查找这个方法;否则,在类C实现的接口列表中递归查找此方法。如果找到匹配的方法,则说明类C是一个抽象类。搜索结束后抛出java.lang.AbstractMethodError异常;否则,搜索失败并抛出java.lang.NoSuchMethodError异常;确认;(4)接口方法分析接口方法还需要分析接口方法表的class_index所属的类或接口引用。如果解析成功,则用C表示这个类或接口,虚拟机按照以下规则进行查找:直接抛出java.lang.IncompatibleClassChangeError异常;否则,在接口C中查找该方法;否则,在接口C的父接口中递归查找;否则,搜索失败,并抛出java.lang.NoSuchMethodError异常;返回成功后不会验证权限,因为接口的方法是public的;5.初始化阶段类初始化阶段是类加载过程的最后一步,除了用户应用程序在加载阶段可以通过自定义类加载器参与外,其余的动作完全由虚拟机主导和控制。在初始化阶段,实际执行类中定义的Java程序。前面我们知道,变量在准备阶段已经被赋了一次系统初值,而在初始化阶段,类变量等内容会根据程序员通过程序制定的主观计划进行初始化。即初始化阶段是执行类构造函数
