美文网首页程序员首页投稿(暂停使用,暂停投稿)
[Class文件结构]3——方法表、属性表

[Class文件结构]3——方法表、属性表

作者: 某昆 | 来源:发表于2018-01-01 12:51 被阅读292次

    前言

    [Class文件结构] 2 - 常量池、字段表对常量池、字段表等进行了阐述,本文继续分析Class文件,阐述方法表、属性表等。

    方法表

    方法表结构如下图所示:

    和字段表类似,依次包含访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。

    因为volatile关键字和transient关键字不能修饰方法,所以方法表中的访问标志没有这两个。与之相对的,synchronized、native、strictfp和abstract关键字可以修饰方法,方法表标志位取值如下:

    查看示例Class文件,第1个u2类型的数据值为2,表示集合中有2个方法(一个是类默认的init构造方法,另一个是inc方法)。第1个方法的访问标志为1,则是使用了ACC_PUBLIC的标志,名称索引为7,查看常量池可知方法名为 <init> ,描述符索引值为8,对应常量为 ()V ,属性表计数器 attrbutes_acount值为1,表示此方法有一个属性,属性名称索引为9,对应常量为 Code。关于Code,下文会详述。

    对照 javap 指令结果查看,更清晰。

    Code,代码,顾名思义就是存储方法中的代码,Java代码经过编译器编译成字节码指令之后,存放在Code属性中。并不是直接存储的Java代码。

    属性表集合

    属性表(attribute_info)在Class文件、字段表、方法表中都可以携带自己的属性表集合,以用于描述某些场景专有信息。

    与其它数据项目要求的顺序、长度、内容不同,属性表集合的要求稍微宽松,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,编译器还可以向属性表中写入自已定义的属性信息,Java虚拟机能忽略掉它不认识的属性。下图是 Java虚拟机规范 一文中预定义的9项虚拟机能识别的属性。

    对于每个属性,它的名称需要从常量池中引用一个 CONSTANT_Utf_info 类型的常量来表示,而属性值的结构则是完全自定义的,只需要说明属性所占用的长度即可,一个规范的属性表应该满足下面所示结构。

    Code属性

    Java方法内代码经过Javac编译处理后,最终变成字节码指令存储在Code属性内。Code属性出现在方法表的属性集合中,但并非所有方法表都必须存在这个属性。接口或抽象类的方法就不存在Code属性。Code属性结构如下:

    attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为Code。

    attribute_length指示了属性的长度。

    max_stack代表了操作数栈深度的最大值,在方法执行的任意时刻,操作数栈不会大于这个深度。

    max_locals代表了局部变量所需要的存储空间。max_locals单位是slot,slot是虚拟机为局部变量分配内存所使用的最小单位。对于byte,char,float,int,short,boolean,reference,return Address 等长度不超过32位的数据类型,每个局部变量使用1个slot,而double和long这两种64位数据类型则使用2个slot。注意,slot可以重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占用的slot就可以被其它局部变量使用。

    code_length和code用于存储Java源程序编译后生成的字节码指令。code_length代表字节码长度,code用于存储字节码指令的一系列字节流。code由u1表示,虚拟机讲到一个字节码时就知道怎么理解,后续带什么参数等等。u1的取值是0到255,也就是说一共可以表达255条指令。

    code有点类似cpu上的指令集,例如+号会被编译成 iadd 虚拟机字节码指令。

    code_length由一个u4表示,理论上最大值是232-1,但虚拟机规范中限制方法不能超过65535。

    以示例Class为例,分析其init方法。

    max_stack和max_locals值都为1,code_length值为5,则表示接下来有5个字节码指令。

    字节码指令的具体定义,请自行搜索查询,较为繁杂,本文中不再细说,大体情况如上文所言,将一行行Java代码通过编译成特定的指令集,虚拟机能懂的指令集。对照 javap 查看init方法内容

    有读者可能会问了,明明是5个指令,为啥上边只显示3个指令,0、1、4还有2和3哪去了呢?00和0A是方法的参数,0A通过常量池很容易就能查询到,是init方法的描述符。而00在常量池中没有,其实它表示this指针。

    注意到 javap 内容中显示的args_size = 1,明明init方法没有参数,怎么会值为1呢?其实就是因为非static函数自带this指针为参数,可以访问对象本身。

    异常表

    异常表就是方法中的异常处理内容,try catch代码块。它的结构如下所示:

    它表示,如果字节码从start_pc行到end_pc行之间出现类型为catch_type或其子类的异常,则转到handler_pc行继续处理。具体字节码指令不再分析。

    Exceptions属性

    Exceptions是在方法表中与Code属性平级的一项属性,与异常表不一样,异常表是Code的下级属性。Exceptions属性的作用是列举出方法中可能抛出的受查异常,也就是 throws 关键字后列表的异常,它的结构表如下:

    number_of_exceptions项表示方法有可能抛出多少种异常,每一种异常由一个exception_index_table项表示,exception_index_table是一个指向常量池中 utf8 类型的索引。

    LineNumberTable属性

    LineNumberTable属性用于描述 Java 源码行号与字节码行号 之间的对应关系。它不是必须属性,如果不生成它,那么产生异常后,堆栈中将不会显示出错的行号,并且在调试时也无法在源码中设置断点。

    line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包括了start_pc和line_number两个u2数据项,前者是字节码行号,后者 是Java源码行号。

    查看javap内容:

    javap中显示 line 8:0,第8行正好是return所在的行号。

    LocalVariableTable属性

    LocalVariableTable用于描述栈桢中局部变量表中的变量与Java源码中定义的变量之间的关系。也不是必须的,它的结构如下:

    其中local_variable_info项目代表了一个栈桢与源码中局部变量的关联,结构如下:

    start_pc和length属性分别代表这个局部变量的生命周期的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。

    name_index和descriptor_index都是指向常量池中 utf8 类型的常量,分别代表局部变量的名称及局部变量的描述符。

    index是这个局部变量在栈桢局部变量表中slot的位置,如果是64位类型,则它占用的slot为index 和 index+1的两个位置。

    如果将inc方法中添加一个参数,如下图所示:

    public int inc(int a){
        return m + 1;
    }
    

    查看javap内容

    LocalVariableTable说明了方法内局部变量的名字,占用空间以及描述符(参数是什么类型等等)。

    SourceFile属性

    SourceFile属性,用于记录Class文件的源码文件名称。

    其它

    属性表还有几个属性,比如 ConstantValue和 Inner Class。可以自己写代码,查看javap内容学习。值得一提的是,对于类变量也就是static变量,如果变量被static和final同时修饰并且是基本数据类型或者String类型的话,那么此变量使用 ConstantValue 属性进行初始化,如果此变量没有被final修饰,或者并非基本类型及字符串,则选择在 <clinit> 方法中进行初始化。

    总结

    Class文件结构真心复杂,很多都是要记忆的,也很枯燥,但是要学习虚拟机原理,必须清楚Class文件的结构,幸好有javap这个工具,多看看javap信息,有助于理解Class文件内容。

    相关文章

      网友评论

        本文标题:[Class文件结构]3——方法表、属性表

        本文链接:https://www.haomeiwen.com/subject/kjxtnxtx.html