Class文件格式(3)-Constant && Code

1.ConstantValue

    ConstantValue属性是定长属性,位于field_info结构项的属性表中,该属性描述了一个常量字段的值。field_info结构项中最多只能够有一个ConstantValue属性。如果field_info结构项所描述的字段是static字段,也即描述该字段的field_info的access_flags被设置了ACC_STATIC标记,则field_info所描述的字段将被初始化成field_info属性表中ConstantValue属性所描述的常量值,这个初始化过程在发生在类或者接口的初始化方法()之前。
    如果拥有ConstantValue属性的field_info结构项描述的是一个非静态字段,也即描述该字段的field_info的access_flags未被设置ACC_STATIC标记,则该ConstantValues属性将被JVM忽略。
    所有JVM的实现都必须能够识别ConstantValue属性,ConstantValue属性的格式如下所示:
Class文件格式(3)-Constant && Code_第1张图片

    1.1、attribute_name_index

        u2类型整数,指向常量池的有效索引,该索引处存放CONSTANT_Utf8_info常量项,描述字符串“ConstantValue”。

    1.2、attribute_length

        u4类型整数,固定值为2,表示ConstantValue_attribute属性项的实际内容占用2个字节的空间,也即constantvalue_index所占用的字节数。

    1.3、constantvalue_index

        u2类型整数,描述常量的具体指,该整数为常量池的有效索引,根据长两属性所描述的常量类型不同,该索引处可以存放CONSTANT_Long、CONSTANT_Integer、CONSTANT_Float、CONSTANT_Double、CONSTANT_String常量项,其中,int、short、char、byte、boolean均用CONSTANT_Integer常量项描述。

2.Code

    Code属性为变长属性,位于描述方法的结构项method_info的属性表中,Code属性存储单个普通方法、实例初始化方法、类或者接口的初始化方法的JVM指令以及相关辅助信息。所有的JVM实现都必须识别Code属性。如果某个方法是native或者abstract,则描述该方法的method_info结构项的属性表中不能有Code属性,否则,method_info结构项的属性表中都必须存在一个Code属性,用于描述该方法的指令集。
    Code属性的格式如下图所示:
Class文件格式(3)-Constant && Code_第2张图片

    2.1、attribute_name_index

        u2类型整数,指向常量池的有效索引,该索引处存放CONSTANT_Utf8_info常量项,描述字符串“Code”。

    2.2、attribute_length

        u4类型整数,表示Code属性内容所占用的字节数。

    2.3、max_stack

        Code所属的方法在任意运行时刻的操作数栈的最大深度。

    2.4、max_locals

        描述分配在Code所属方法所引用的局部变量表中的局部变量个数,包括调用此方法时,传递参数的局部变量。
        由于long和double类型占用局部变量的两个槽位,因此long和double类型的局部变量的最大索引是max_locals-2,其他类型的局部变量的最大索引为max_locals-1。

    2.5、code_length

        大于零的u2类型整数,描述code[]数组的字节数。

    2.6、code[]

        非空数组,存储实现Code所属方法的JVM字节码指令。
        JVMS7对code[]数组中的部分字节码指令存在一些约束条件,本文后续会给出说明。

    2.7、exception_table_length

        exception_table[]异常表元素个数。

    2.8、exception_table[]

        Code属性所属方法的异常处理表,表中的每个元素为一个异常处理器结构项,表中元素之间的顺序不能随意更改,异常处理器结构项的布局如下:
Class文件格式(3)-Constant && Code_第3张图片
        结构项中各个域的具体含义如下:

        2.8.1、start_pc

            code[]数组的一个有效索引,该索引处的字节必须为一个合法的JVM操作码指令,标记异常触发区域的起始点。

        2.8.2、end_pc

            code[]数组的一个有效索引,该索引要么等于code_length,要么code[]数组在该索引处的字节必须为一个合法的JVM操作码指令,标记异常处罚区域的结束点。
            end_pc配合start_pc,指明了code[]数组中异常的触发区域,start_pc的值必须小于end_pc,start_pc和end_pc组成一个左闭右开的异常触发区域[start_pc,end_pc),这种左闭右开是由于JVM历史上的一个设计缺陷引起的,本文不做过多的谈论,感兴趣的读者可以阅读JVMS7的Code属性相关章节。

        2.8.3、handler_pc

            code[]数组的一个有效索引,该索引处的字节必须为一个合法的JVM操作码指令,标记在[start_pc,end_pc)区域内触发的异常所对应的异常处理器的起始点。

        2.8.4、catch_type

            u2类型的整数,指向常量池的有效索引,该索引处存放CONSTANT_Class_info常量项,描述当前异常处理器指定捕获的异常类型。
            只有当抛出的异常时指定异常类或者其子类的实例时,对应的异常处理器才会被调用。
            如果catch_type的值为0,则此异常处理器会在所有异常抛出时都被调用。以此可以实现finally语句。

    2.9、attributes_count

        Code属性中包含的属性个数。

    2.10、attributes[]

        Code属性的属性表数组,数组中的每个元素都是attribute_info结构项,描述Code的属性信息,一个Code属性可拥有多个属性与之关联。
        JVMS7规定,能在Code属性的属性表中出现的属性类型有:LineNumberTable、LocalVariableTable、LocalVariableTypeTable、StackMapTable,具体如下:
Class文件格式(3)-Constant && Code_第4张图片
        JVMS7还规定,如果JVM的实现支持50.0或者更高版本的Class文件,则该JVM必须能够从版本号为50.0或者更高的Class文件中识别并正确读取那些存在于Code属性的属性表中的StackMapTable属性。

3.code[]约束

    JVM将方法、实例初始化方法()、类或者接口的初始化方法()的JVM字节码存放于Class文件的mehtod_info结构项的Code属性的code[]数组中,JVMS7对Code属性的code[]数组中存放的这些JVM字节码给出了一些约束条件:静态约束(Static  Constraints)、结构约束(Structural Constraints)。

    3.1、Static Constraints

        静态约束规定了JVM字节码指令在code[]数组中的布局方式以及相关指令的操作数内容。

        3.1.1、指令相关

            与code[]数组中的JVM字节码指令相关的静态约束主要有如下几条:
            1、code[]数组不能够为空,对应的,code_lenght不能够为0。
            2、code_length的值必须小于65536。
            3、code[]数组中的第一条指令的操作码是从数组中索引为0处开始的。
            4、只有JVMS中规定的指令才能够出现在code[]数组中。
            5、如果Class文件的版本号为51.0或者更高,则jsr、jsr_w指令不能够出现在code[]数组中。
            6、code[]数组中,除最后一条指令,对于其它指令来说,后一条指令的索引等于当前指令操作码对应的索引加上当前指令的长度再加上当前指令操作数的长度。对于wide指令而言,跟随在wide指令之后的操作码会被当成wide指令的操作数,程序调整时,不能够直接跳转到该操作码。
            7、code[]数组中最后一个字节的索引必须是code_length-1。

        3.1.2、操作数相关   

            与code[]数组中JVM字节码指令的操作数相关的静态约束有:
            1、所有跳转和分支指令(jsr、jsr_w、goto、goto_w、ifeq、ifne、ifle、iflt、ifgt、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmple、if_icmplt、if_icmpge、if_icmpgt、if_acmpeq、if_acmpne)的跳转目标必须是当前法内某条指令的操作码。但跳转与分支指令的目标一定不能是某条被wide指令修饰的指令的操作码,跳转与分支指令的跳转目标可以是wide指令本身。
            2、tableswitch指令的每个分支的跳转目标(包含default目标)都必须是当前方法内的某条指令的操作码。每个tableswitch指令必须在它的跳转表中包含与自己low和high跳转表操作数的值相等的条目,并且low值必须小于等于high值。tableswitch指令的跳转目标不能是某条被wide指令修饰的指令的操作码,tableswitch的跳转目标可以是wide指令本身。
            3、lookupswitch指令的每个分支的跳转目标(包含default目标)都必须是当前方法内的某条指令的操作码。每个lookupswitch指令都必须包含与自己npairs操作数的值相等数量的match-offset值对。match-offset值对须升序排列。lookupswitch指令的跳转目标不能是某条被wide指令修饰的指令的操作码,lookupswitch指令的跳转目标可以是wide指令本身。
            4、每个ldc和ldc_w指令的操作数都必须是常量池的有效索引,该索引处的常量项必须为如下类型:
                如果Class文件版本号小于49.0,常量项可为CONSTANT_Integer,CONSTANT_Float或CONSTANT_String。
                ②如果Class文件版本号是49.0或50.0,常量项可为CONSTANT_Integer,CONSTANT_Float,CONSTANT_String、CONSTANT_Class。
                ③如果Class文件版本号是51.0,常量项可为CONSTANT_Integer,CONSTANT_Float,CONSTANT_String,CONSTANT_Class,CONSTANT_MethodType、CONSTANT_MethodHandle。
            5、ldc2_w指令的操作数都必须是常量池的有效索引,该索引处存放CONSTANT_Long_info或者CONSTANT_Double_info常量项,由于Long和Doulbe占用两个槽位,因此紧随该常量池索引之后的那个常量项不能够被单独的使用。
            6、getfield、putfield、getstatic和putstatic指令的操作数都必须是常量池的一个有效索引,该索引处存放CONSTANT_Fieldref_info常量项。
            7、invokevirtual、invokespecial和invokestatic指令的indexbyte operands所构成的整数必须是常量池的一个有效索引,该索引处存放CONSTANT_Methodref_info常量项。
            8、invokedynamic的前两个操作数所构成的整数必须是常量池的一个有效索引,该索引处存放CONSTANT_InvokeDynamic_info常量项,invokedynamic指令的第三和第四个操作数字节必须为0。
            9、只有invokespecial指令可以调用实例初始化方法。类或者接口的初始化方法()不会被Java虚拟机指令显示调用,它是由Java虚拟机自己来隐式调用。
            10、由invokeinterface指令的的前两个indextype operands构成的整数必须是常量池的一个有效索引,该索引处存放CONSTANT_InterfaceMethodref_info常量项,invokeinterface指令的count操作数值必须反映出传入接口方法的参数所占用的局部变量的个数。invokeinterface指令的第四个操作数字节必须是0。
            11、instanceof、checkcast、new、anewarray、multianewarray指令中的索引操作数必须为常量池的有效索引,该索引处存放CONSTANT_Class_info常量项。
            12、anewarray指令创建的数组的维度不能够超过255。
            13、new指令不能够用于创建数组,创建数组用anewarray、multianewarray指令
            14、multianewarray指令dimensions操作数值指明了该指令所创建的数组的最大维度,而且dimensions的值不能够为0。
            15、newarray指令的atype操作数取值可为:T_BOOLEAN(4)、T_CHAR(5)、T_FLOAT(6)、T_DOUBLE(7)、T_BYTE(8)、T_SHORT(9)、T_INT(10)、T_LONG(11)。
              16、 iload、fload、aload、istore、fstore、astore、iinc、ret指令的索引位操作数必须是非负整数且不能大于max_locals-1。
            17、iload_、fload_、aload_、istore_、fstore_、astore_指令的显示索引不能大于max_locals–1。
               18、lload、dload、lstore、dstore指令的索引位操作数不能大于max_locals–2。
              19、 lload_、dload_、lstore_、dstore_指令的显式索引不能大于max_locals–2。
              20、  修饰iload、fload、aload、istore、fstore、astore、ret、innc指令的wide指令的索引位操作数必须为非负整数,且不能大于且max_locals–1。修饰lload、dload、lstore、dstore指令的wide指令的索引位操作数必须为非负整数,且不能大于max_locals–2。

    3.2、Structural Constraints

        结构化约束用于限制JVM字节码指令之间的关系,具体如下:
        1、所有指令的执行,都必须基于操作数栈和局部变量表中有适当类型、适当个数的参数, 执行时不必关心触发指令被调用的执行路径。能够操纵int类型的指令,也能够操纵boolean、byte、char、short类型,而且JVM内部会将boolean、byte、char、short转化为int进行处理。
        2、不论通过何种执行路径触发了指令的调用,该指令的操作数栈必须拥有相同的深度。
        3、在执行的任意时刻,long、double类型在局部变量表对中的存储属性不能够被倒置或者分割,同时,这样的局部变量表对不可单独分开访问。
        4、局部变量在未正式赋值之前不可被访问。
        5、在执行的任意时刻,操作数栈不允许增长到超过max_stack。
        6、在执行的任意时刻,不允许从操作数栈中抛出比它全部数据还多的数据。
        7、每个invokespecial必须指明一个属于当前类或者其父类的实例初始化方法。
        8、调用实例初始化方法时,那个还未被初始化的实例必须存放在操作数栈上适当的位置。如果一个实例已经被初始化过,那就不允许再调用它的实例初始化方法。
        9、在对象未被初始化之前,对象所包含的实例方法和实例变量不能够被调用和访问。
        10、在操作数栈或局部变量表上不允许有未被初始化的类变量作为回向分支(Backwards Branch)的目标,除非在分支指令上有特殊的未初始化实例类型已经在分支的目标上与自身归并。
        11、被异常处理器所保护的代码中,局部变量表内不允许出现未被初始化的实例。
        12、当jsr或jsr_w指令执行时,在操作数栈或局部变量表中不允许出现未被初始化的实例。
        13、除了从Object类继承而来的实例初始化方法以外,在实例的成员被访问之前,所有的实例初始化方法都必须调用this()实例初始化方法或者直接父类的super()实例初始化方法来初始化实例。但是,在调用任何实例初始化方法之前,声明在当前类中的实例字段this应当被赋值。
        14、方法调用的参数都必须与方法描述符相兼容。
        15、方法调用指令的目标实例的类型必须与指令所指定的类或接口的类型相兼容。除非某个实例初始化方法正在被调用,否则每个invokespecial指令的目标类型必须与当前类相兼容。
        16、所有返回指令都必须与其方法的返回类型匹配。
            ①如果方法的返回类型是boolean,byte,char,short或int就只能使用ireturn指令。
            ②如果方法返回float,long或doule类型,就分别使用freturn,lreturn或dreturn指令。
            ③如果方法返回reference类型,就必须使用areturn指令,并且返回值的类型必须与方法的返回描述符的类型相兼容。
            ④所有的实例初始化方法,类或接口初始化方法和声明返回void的方法都必须使用return指令返回。
        17、如果通过getfield或putfield访问与当前类不在同一运行时包的父类中的protected字段,那么正在被访问的实例类型必须与当前类或当前类的子类是相同的。如果通过invokevirtual或invokespecial访问与当前类不在同一运行时包的父类中的protected方法,那么正在被访问的实例类型必须与当前类或当前类的子类是相同的。
        18、通过getfield或putfield指令访问和操作的实例类型必须与指令所指定的类型相兼容(JLS §5.2)。
        19、通过putfield或putstatic来保存的值的类型必须与正在操作的实例或类的字段描述符相兼容。
            ①如果描述符类型是boolean,byte,char,short或int,那么值就一定是int类型。
            ②如果描述符类型是float,long或double,那么值就分别是float,long或double。
            ③如果描述符是reference类型,那么值的类型就必须与描述符类型相兼容。
        20、由aastore指令存储到数组中每个值都必须是引用类型。正在被aastore指令存储的数组的组件类型也必须是引用类型。
        21、athrow指令只能抛出Throwable类或它的子类的实例。所有出现在方法异常表中catch_item项的类都必须是Throwable或它的子类。
        22、程序执行时,不允许执行超过code[]数组末端。
        23、返回地址(returnAddress类型值)不可以从局部变量表中加载。
        24、在jsr或jsr_w之后的指令可由单个ret指令来返回。
        25、当某程序子片段(Subroutine)已经出现在其他程序子片段调用链上时,跳转入该程序子片段的jsr或jsr_w指令就不能作为另一个程序子片段的返回地址,因为这样会产生无限递归的情况。
        26、returnAddress类型的实例至多会返回一次。


你可能感兴趣的:(Java,&&,JVM)