JAVA的类加载过程

我们详细讲解一下Java虚拟机中类加载的全过程,也就是加载、验证、准备、解析和初始化这5个阶段所执行的具体动作。

一、加载
“加载”是“类加载”(Class Loading)过程的一个阶段,希望读者没有混淆这两个看起来很相似的名词。在加载阶段,虚拟机需要完成以下3件事情:

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

二、验证
    验证阶段大致上会完成下面4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

    1) 文件格式验证
    第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。这一阶段可能包括下面这些验证点:
    . 是否以魔数0xCAFEBABE开头。
    . 主、次版本号是否在当前虚拟机处理范围之内。
    . 常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。口指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
    . CONSTANT Utf8info型的常量中是否有不符合UTF8编码的数据。
    . Class文件电各个部分及文件本身是否有被删除的或附加的其他信息。

    2) 元数据验证
    第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java 语言规范的要求,这个阶段可能包括的验证点如下:
    . 这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
    . 这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
    . 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
    . 类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。

    3) 字节码验证
    第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件

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

三、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
publicstatic int value=123;
那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,而把value 赋值为123的putstatic 指令是程序被编译后,存放于类构造器<clinit>0方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。表7-1列出了Java中所有基本数据类型的零值。

    上面提到,在“通常情况”下初始值是零值,那相对的会有一些“特殊情况”:如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设上面类变量value的定义变为:
    public static final int value=123;

    编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。

四、解析

    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在前一章讲解Class文件格式的时候已经出现过多次,在Class文件中它以CONSTANT_Class info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现,那解析阶段中所说的直接引用与符号引用又有什么关联呢?
   . 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
   . 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄
和调用点限定符7类符号用进行,分别对应于常量池的CONSTANT-Class info、CONSTANT Fieldref info、CONSTANT_Methodref info-CONSTANT_InterfaceMethodref info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info 和CONSTANT InvokeDynamic_infoT种常量类型。

五、初始化

    类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>O方法的过程。我们在下文会讲解<clinit>O方法是怎么生成的,在这里,我们先看一下<clinit>0方法执行过程中一些可能会影响程序运行行为的特点和细节,这部分相对更贴近于普通的程序开发人员。
    初始化顺序请参考文章:https://www.cnblogs.com/mrhgw/p/13050024.html

posted @ 2021-03-10 10:32  有容乃大  阅读(516)  评论(0编辑  收藏  举报