JVM之类文件结构——下篇(字段、方法和属性)

来源:互联网 发布:学历网络教育培训班 编辑:程序博客网 时间:2024/06/06 10:55

LZ水平有限,如果发现有错误之处,欢迎大家指出,或者觉得那块说的不好,欢迎建议。希望和大家一块讨论学习
LZ QQ:1310368322


上篇我们着重讨论了class文件中的常量池,接下来我们来看看下面的十六进制数到底是什么含义?以及它们和我们的Java源码之间有什么联系?
访问标志
常量池之后,紧接着的两个字节代表了访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,后面还会有关于方法的访问标志。
下面是类或接口访问标志的一些标志值和含义
这里写图片描述
我们先看看class文件中的这个标志值为多少
这里写图片描述
0x00 21,表中没有这个值啊,这是为什么呢?其实这是因为有时候这个访问标志有可能同时满足好几种类型,所以就把他们的标志值进行了或运算了,我这个HelloWorld 类是 public 的,所以我满足 ACC_PUBLIC, 值为 0x00 01,又因为我的JDK的版本是 1.7 的,所以我满足 ACC_SUPER, 值为 0x00 20,两者相或: 0x0001 | 0x0020 = 0x0021.
我们再来看看我们的HelloWorld的源码中的类访问标志
这里写图片描述

类索引、父类索引与接口索引集合
接下来就是类索引、父类索引和接口索引集合了,类索引和父类索引都是一个U2类型[占两个字节]的数据,而接口索引集合是一组U2类型的数据的集合(相当于一个存放U2类型的数组)。Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的权限定名[上篇已经介绍过了],父类索引用于确定这个类的父类的权限定名。而对于接口索引集合,在父类所以之后的两个字节代表着接口计数器,代表着接口索引的容量(个数),这里的HelloWorld不牵扯接口,所以这个值为0
下来看看Class文件中的类索引、父类索引和接口索引集合
这里写图片描述
我们看到类索引的值是0x00 01,上面已经说过,这个类索引是确定这个类的全限定名的,那是如果“确定”的呢?很容易相当,这些全限定名是存储在常量池中的,肯定是通过这个索引去找的。如下图
这里写图片描述
在javap 反编译中的常量池中是这样的
这里写图片描述
父类索引找权限定名的方法一样,由于没有接口,所以这里不再讨论
字段表集合
接下来就是关于字段的一些信息了,字段表用于描述接口或者类中声明的变量, 我们想一想,Java中的字段的信息在Class中如何描述呢?字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(是否被static修饰)等等,这些信息用标志位来描述是很自然方便的,比如说,我用0x00 08代表被static修饰。而一些字段的名字、字段的数据类型(包括自定义数据类型)等等,这些很难固定(情况很多),所以我们只用将其存到常量池中,引用常量池来描述。
下来我们看看一个字段都包含哪些东西
这里写图片描述
其中的第一项Access_flags 对应的字段访问标志表如下
这里写图片描述
第二项的name_index 和 第三项的 descriptor_index 分表代表着字段的名称索引 和 字段的描述符索引
字段的名称索引很好理解,就是指向字段的名称(在常量池中)的索引,而字段的描述符是什么?这里详细的说说
比较严谨的描述是:描述符的作用是用来描述字段的数据类型。方法的参数列表(包括数量、类型以及顺序)和返回值,这个可能不是很直观,简单来说,对于字段而言,就是字段的类型,比如 public int i = 0; 其中 int 类型就是该字段的描述符,在Class文件中 , int 类型用 字符“I”来表示,所以字段描述符的索引就是指向了该字段的描述符(在常量池中),字段中还有可能有属性,比如说,你的代码中有final static int i = 123; 这个字段就会有一个ConstantValue属性,其值指向常量123.
我们之前写的HelloWorld没有字段,我们现在添加一个字段,如下:
这里写图片描述
然后我们在来看看Class文件有什么变化
这里写图片描述
我们可以看到,后面多了字段的一些信息了,其中的 fields_count 值为0x00 01,说明我们的字段只有一个,access_flags值为0x00 01,查表可知我们这个字段是被public修饰的,name_index的值为0x00 05,说明这个字段的名称是在常量池的第五项常量中存着,用javap反编译一下,如下图:
这里写图片描述
我们可以看到字段的名称为 i, 同时 字段的描述符为 I(int 类型的)
因为我们这里的字段没有属性,所以attributes_count的值就为0,后面就没有attribute_info了
方法表集合
方法表和字段表非常相似,上面的字段表比较简单,没有属性,接下来我们讨论的方法表有属性,稍微有点绕,但是仔细研究就清楚了。
同属性表一样,我们先来看看方法表中都有什么东西(方法表结构),如下图:
这里写图片描述
可以看到,这个表结构和字段的表结构完全一样,其含义就不再赘述了。有的读者可能会问:方法和字段不太一样啊,方法体里面还有好多代码,这些代码对应在字节码中的哪里?其实这些代码就是在方法表中的属性中,有一个“Code”属性专门存这些执行指令。
同样方法的访问标志表如下:
这里写图片描述
由于前面的HelloWorld没有声明方法,所以我们修改源代码,加上一个简单的 fun 方法
如下:
这里写图片描述
还是老方法,我们看看源码对应的字节码,以及字节码都代表什么意思?
这里写图片描述
和属性表一样,最前面的是methods_count,可以看出,Class文件中有三个方法,大家应该能猜到都要啥方法吧,构造方法、fun方法和main方法。在具体分析这个方法里的一些细节问题,我们先来看几个常用的属性
① Code属性: Java程序方法体中的代码经过Javac编译处理后,最终变为字节码指令存储在Code属性内。Code属性在方法表的属性集合之中
它的结构如下:
这里写图片描述attribute_name_indexCode属性的名字的索引,在常量池中,值为Code,attribute_length代表Code属性的长度,注意:这里的长度不包含attribute_name_index 和 attribute_length本身的长度,也就是说整个Code属性表的长度减去attribute_name_index和attribute_length本身所占的字节数就是Code属性值的长度。
下来简单介绍一个下面各个字节的含义,这些含义在下次的执行引擎中会详细的介绍。
max_stack: 操作数栈的最大值
max_locals:局部变量表所需的存储空间
code_length: code值得长度
code:方法体中的代码编译后的字节码
exception_info: 异常信息
attribute_info: Code 属性的属性(可以看成子属性)
② LineNumberTable属性:
属性结构如下图
这里写图片描述
[稍后详细介绍]
③ LocalVariableTable属性:
属性结构如下图:
这里写图片描述
对照着方法表的结构和这些属性(主要是看占了几个字节),我们来分析一下第一个方法的字节码文件,由于WinHex软件中地方太小,我在其他软件重新画了一个图,如下:
这里写图片描述
其中的一些索引(如name_index、descriptor_index等),都是可以在常量池中查出来的。
其中Code属性在javap 中如下图:
这里写图片描述
接下来我们来详细看看LineNumberTable中的东西
这里写图片描述
上文贴的LineNumberTable属性结构中的line_number_info,这里说明一下,line_number_info 表包括了 start_pc 和 line_number 两个 u2 类型的数据项,前者是字节码行号,后者是Java源码行号,这里的字节码行号指的是code属性中code值中的字节码偏移量,也就是上面用Javap打印出Code属性中code值最左边的数字(后面有个冒号),这里要特别注意: javap 打印出来的 LineNumberTable 中 line 3 : 0,前面的是Java源码中的行号,后面对应的是字节码行号,而在字节码中的一对行号(字节码行号和Java源码行号)的顺序是:前面是字节码行号后面是Java源码行号,如下:
这里写图片描述
这里写图片描述
然后我们再来详细看看LocalVariableTable属性
将方法表中的LocalVariableTable字节码截取下来,根据LocalVariableTable的属性结构和local_variable_info项目结构(上面已经给出),可以画出下面的图
这里写图片描述
这里需要注意一个概念,Slot,Slot是局部变量中最小的单元,对于byte、char这样长度不超过32位的数据类型,在局部变量中占一个Slot,而double和long这样64位的数据类型则需要两个Slot来存放
Slot在一个线程的函数栈帧中,如下图
这里写图片描述
还有一个需要理解的就是,start_pc和length属性,start_pc指的是这个局部变量的声明周期开始的字节码偏移量,其实就是Code属性中真正的code值中的字节码偏移量,我们这个函数的start_pc的值为0x00 00,就是说我这个局部变量(this)的声明周期是从0: aload_0[这个就是前面用javap打印出来的Code属性中的code值得第一行] 处开始,然后length指的是我这个局部变量的作用范围的长度,其实就是我从字节码的开始c处,我能够作用于多长的字节码,这个构造函数的this局部变量的length为:0x00 0A,长度为10,就是整个code值中的所有指令我都可以作用得到,从0: aload_0 到 9: return,这两个合起来的意思就是这个局部变量在字节码之中的作用域范围。name_index和descriptor_index和前面所代表的的意思一样,还有一个index是局部变量在栈帧局部变量表中Slot的位置。
最后把这几个属性串起来,他们的关系如下:
这里写图片描述
最后再看一下一个小属性,也是这个Class文件的最后的东西了
老规矩,先看一下SourceFile属性的属性结构
这里写图片描述
sourcefile_index是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名。
这里写图片描述
注意:方法表之后紧跟着是属性的个数(attributes_count)
其中的Code属性会在下一篇执行引擎详细介绍

本文参考《深入理解Java虚拟机》—周志明

阅读全文
1 0
原创粉丝点击