我们的口号是:每天进步一点点!
上一篇的字段写到属性的时候,由于属性涉及到的内容比较多,我们暂时搁置了,这一篇是Class文件结构的最后一篇,我们就详细说说属性表结合。
九、属性(Attribute):从表中看属性部分分为属性个数(attributes_count)和属性信息(attributes),这个已经没什么好说的了(参考前面的文章),这个单独拿出来主要是属性的类型比较多,如下图所属。类中字段和方法都会用到属性中的信息。
下面我们看一下属性信息(attributes)的结构。
注意!这只是一个基本模型,Java虚拟机没有对属性信息做明确的规定,需要有哪些属性,只要满足有一个u2的名称索引和一个u4的属性信息所占长度就可以,剩下的可以*定义(最后一刻还是暴露了本性,真没想到你是这样随便的虚拟机)。
对于第一项属性名称的索引(attribute_name_index),相信大家也看出了一个区别,不再是使用“类型-标志”对应的形式了,而是直接存储了属性名称(不是很理解,直接存数字就可以了啊),不知道为什么如此直接。
还得再上这张图……以测试程序为例,attribute_name_index为“00 09”,那么就是指向#9,也就是“ConstantValue”,那我们就顺势扑倒ConstantValue属性吧。
ConstantValue:常量值,如果类中某个字段中包含了这个属性,说明这个字段是静态常量,肯定是static的;
如下图所示的ConstantValue结构,由于的第三个属性是u2类型的索引,所以attribute_length固定为“2”(00 00 00 02)。
让我们再来看一下字节码文件,constantvalue_index为“0a”,指向#10,值是2,为Integer类型。
下面让我们再介绍几个比较常用的属性。
1. Exceptions:方法中throws关键字后面的异常类型集合。
数据结构如下,包含名称( attribute_name_index,就是Exceptions),number_of_exceptions指定了异常个数,exception_index_table是指向常量的索引。
2. Code:存储程序方法体中的代码。
既然是方法体,那接口中肯定是没有这个属性的。
code的结构如下图所示,包含的类型有点多,前两项相信大家已经知道是什么意思了,我们从第三项开始。
max_stack:代表操作数栈深度的最大值。如果超过了,还记得被*Error异常支配的恐惧吗?
max_locals:局部变量表所需的空间。单位是slot(jvm为局部变量分配内存所使用的最小内存),基本数据类型长度不超过32的占用1个slot,64位的(long,double)占两个slot。
注意,这个max_locals并不是单纯的方法内各局部变量相加之和,当某一个变量声明周期结束的时候,它的空间是可以分配给其它变量使用的。
code_length:方法体生成的字节码指令长度。长度虽然占4个字节,但是虚拟机规定一个方法的字节码指令条数不能超过65533。
code:字节码指令内容。其中每个code占一个字节,也就是可以代表256种指令,字节码对应的指令如下标所示。
光说不练假把式,看图说话。
method_info中的attributes_count为“00 01”,attributes则只有一个,字节码为“00 0d”指向#13,即“code”。
属性值长度为“00 00 00 2f”,为47;
max_stack为1,max_locals为1;
指令长度字节码为“00 00 00 05”,也就是说有5条指令。
0x2a——将第一个引用类型本地变量推送至栈顶——这个说的太官方了,简单理解,本地变量我们应该知道,就是变量池中那37个;引用类型相对的是基本数据类型,也就是说该变量引用的是一个类,那很明显就是#5吗,也就是说把#5的地址放在栈顶,#5对应的值是com/xkx/App。
0xb7——调用超类构造方法,实例初始化方法,私有方法——调用APP的超类构造方法(那就是Object了),实例话方法就是init呗,私有方法貌似没有,就不用调了。
0x00——什么都不做——什么鬼?
0x01——将null推送至栈顶——清空栈顶(事了拂衣去,深藏功与名?)
0xb1——从当前方法返回void——这个就比较好理解了。
然后异常没有(注意:这个异常和上面异常不是一回事,这个指的是代码中的try{……}catch(Exception e){……}finally{……}),又有两个属性,不再赘述。
属性的基本原理就是这样,就不一一列举了,剩下的属性,感兴趣的朋友自行查阅吧。
喜欢文章或想一起学习的朋友可以关注我,给我点赞,我将会持续更新,有什么疑问或文中有不当之处请给我留言,真诚地希望能与大家一起交流探讨,学习进步。