深入理解Class中--方法、属性表集合

之前有关class文件已经写了两篇博客:

1、【JVM虚拟机】(5)---深入理解JVM-Class中常量池

2、【JVM虚拟机】(6)---深入理解Class中访问标志、类索引、父类索引、接口索引

3、【JVM虚拟机】(7)---深入理解Class中-属性集合

那么这篇博客主要讲有关 方法表集合 相关的理解和代码示例。

方法表集合: 告知该方法是什么修饰符修饰?是否有方法值?返回类型是什么?方法名称,方法参数,还有就是方法内的一些信息。

一、方法集合概念

1、概念

方法表集合:方法表集合和属性表集合其实很相似,都是由一个计数器(方法)若干个方法表构成,只不过方法表的结构相对复杂很多。

方法表的结构体访问标志(access_flags)、名称索引(name_index)、描述索引(descriptor_index)、属性表(attribute_info)集合组成。

method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}

1)、访问标志

不多说了,和属性中的其实差不多,只是有些修饰符不一样。

2)、名称索引

就是指这个方法的名称。如:'public void getXX()'中,getXX就是名称索引。名称索引两个字节,这个方法的名称以UTF-8格式的字符串存储在这个常量池项中。

3)、描述索引

指这个方法的返回值,方法内参数信息。一个方法的描述包含若干个参数的数据类型和返回值的数据类型。

4)、属性表(attribute_info)集合

下面讲

二、属性表集合

1、概述

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

在方法表中, 属性表集合记录了某个方法的一些属性信息,这些信息包括:

  • 这个方法的代码实现,即方法的可执行的机器指令
  • 这个方法声明的要抛出的异常信息
  • 这个方法是否被@deprecated注解表示
  • 这个方法是否是编译器自动生成的

属性表(attribute_info)结构体的一般结构如下所示:

属性表占着非常大的一部分且定义了众多属性,上面只列举了4个,查看完成的:JDK1.7版本中21项属性表集合简要介绍

下面介绍两个重要的属性

1、Code属性

code属性比较复杂,它是经过编译器编译成字节码指令之后的数据。就是说java程序中的方法体经过javac编译器处理后,最终变成字节码存储在Code属性内

并非所有方法表都有这个属性,接口和抽象类就没有【没有方法体】。
Code属性是Class文件中最重要的一个属性,在Class文件中,Code属性用于描述代码,所有的其它数据项目都用来描述元数据,了解code属性对了解字
节码执行引擎来说是必要基础。

Code属性表的组成部分:

机器指令——code

目前的JVM使用一个字节表示机器操作码,即对JVM底层而言,它能表示的机器操作码不多于28 次方,即 256个。class文件中的机器指令部分是class文件中最重要的部分,并且非常复杂。

异常处理跳转信息

如果代码中出现了try{}catch{}块,那么try{}块内的机器指令的地址范围记录下来,并且记录对应的catch{}块中的起始机器指令地址,当运行时在try块中有异常抛出的话,JVM会将catch{}块对应懂得其实机器指令地址传递给PC寄存器,从而实现指令跳转;

Java源码行号和机器指令的对应关系---LineNumberTable属性表

编译器在将java源码编译成class文件时,会将源码中的语句行号跟编译好的机器指令关联起来,这样的class文件加载到内存中并运行时,如果抛出异常,JVM可以根据这个对应关系,抛出异常信息,告诉我们我们的源码的多少行有问题,方便我们定位问题。

Code属性表结构体:

1、attribute_name_index : 属性名称索引,占有2个字节,其内的值指向了常量池中的某一项,该项表示字符. 串“Code”;

2、attribute_length : 属性长度,占有 4个字节,其内的值表示后面有多少个字节是属于此Code属性表的;

3、max_stack : 操作数栈深度的最大值,占有 2 个字节,在方法执行的任意时刻,操作数栈都不应该超过这个值,虚拟机的运行的时候,会根据这个值来设置该方法对应的栈帧(Stack Frame)中的操作数栈的深度;

4、max_locals 最大局部变量数目,占有 2个字节,其内的值表示局部变量表所需要的存储空间大小;

5、code_length : 机器指令长度,占有 4 个字节,表示跟在其后的多少个字节表示的是机器指令;

6、code 机器指令区域,该区域占有的字节数目由 code_length中的值决定。JVM最底层的要执行的机器指令就存储在这里;

7、exception_table_length : 显式异常表长度,占有2个字节,如果在方法代码中出现了try{} catch()形式的结构,该值不会为空,紧跟其后会跟着若干个exception_table结构体,以表示异常捕获情况;

8、exception_table : 显式异常表,占有8 个字节,start_pc,end_pc,handler_pc中的值都表示的是PC计数器中的指令地址。exception_table表示的意思是:如果字节码从第start_pc行到第end_pc行之间出现了catch_type所描述的异常类型,那么将跳转到handler_pc行继续处理。

9、attribute_count : 属性计数器,占有 2 个字节,表示Code属性表的其他属性的数目

10、attribute_info : 表示Code属性表具有的属性表,它主要分为两个类型的属性表:“LineNumberTable”类型和“LocalVariableTable”类型。“LineNumberTable”类型的属性表记录着Java源码和机器指令之间的对应关系“LocalVariableTable”类型的属性表记录着局部变量描述

2、ConstantValue属性

之所以学习这个,是因为后面类加载机制有联系到这个属性

这个属性的作用是通知虚拟机为静态变量赋值,只要被static修饰的变量才有这个属性,【有该属性的字段必须有ACC_STATIC访问标志,反过来不一定】。
对于 "int x = 123" 和 "static int x =123"这类代码在日常编写中很常见,但虚拟机对这两种变量赋值的时刻却不同。
对于非static变量[实例变量],是在实例构造器<init>进行
对于类变量,有两种方式选择
①在类构造器<clinit>方法中赋值
②使用ConstantValue属性初始化
目前Sun javac编译器是这么做的【具体咋做不知道 = =】,如果同时使用final和static修饰一个变量[这种修饰就相当于个常量],并且是String或基本类型,就使用②,
如果没有被final修饰或不是基本类型和String,就选择①在<clinit>方法中初始化
//有关这点我在上篇博客举过例子,最后几句话也对这个解释的很清楚。

三、示例

有关方法的代码示例,我就不亲自测了,因为有位博主写的已经很清晰啦,我自己写也没那么清晰。

1、访问标志

public static synchronized final void greeting(){
}

greeting()方法的修饰符有:public、static、synchronized、final 这几个修饰符修饰,那么相对地,

greeting()方法的访问标志中的ACC_PUBLICACC_STATICACC_SYNCHRONIZEDACC_FINAL标志位都应该是1

从上面第一张图可以得出,该访问标志的值应该是十六进制0x0039

2、名称索引和描述符索引

紧接着访问标志(access_flags)后面的两个字节,叫做名称索引(name_index),这两个字节中的值是指向了常量池中某个常量池项的索引,该常量池项表示这这个方法名称的字符串。

方法描述符索引(descrptor_index)是紧跟在名称索引后面的两个字节,这两个字节中的值跟名称索引中的值性质一样,都是指向了常量池中的某个常量池项。这两个字节中的指向的常量池项,是表示了方法描述符的字符串

3、代码示例

package com.louis.jvm;
public class Simple {
public static synchronized final void greeting(){
int a = 10;
}
}

1)、 Simple.class文件如下所示

注意 :方法表集合的头两个字节,即方法表计数器(method_count)的值是0x0002,它表示该类中有2 个方法。注意到,我们的Simple.java中就定义了一个greeting()方法,为什么class文件中会显示有两个方法呢?

原因:如果我们在类中没有定义实例化构造方法,JVM编译器在将源码编译成class文件时,会自动地为这个类添加一个不带参数的实例化构造方法,这种添加是字节码级别的,JVM对所有的类实例化构造方法名采用了相同的名称:“”。如果我们显式地如下定义Simple()构造函数,这个类编译出来的class文件和上面的不带Simple构造方法的Simple类生成的class文件是完全相同的。

2)、Simple.class 中的<init>() 方法

解释:

1、方法访问标志(access_flags) : 占有 2个字节,值为0x0001,即标志位的第 16 位为 1,所以该<init>()方法的修饰符是:ACC_PUBLIC;

2、 名称索引(name_index): 占有 2 个字节,值为 0x0004,指向常量池的第 4项,该项表示字符串'<init>',即该方法的名称是'<init>';

3、描述符索引(descriptor_index): 占有 2 个字节,值为0x0005,指向常量池的第 5 项,该项表示字符串“()V”,即表示该方法不带参数,并且无返回值(构造函数确实也没有返回值);

4、属性计数器(attribute_count) : 占有 2 个字节,值为0x0001,表示该方法表中含有一个属性表,后面会紧跟着一个属性表;

5、属性表的名称索引(attribute_name_index) :占有 2 个字节,值为0x0006,指向常量池中的第6 项,该项表示字符串“Code”,表示这个属性表是Code类型的属性表;

6、 属性长度(attribute_length):占有4个字节,值为0x0000 0011,即十进制的 17,表明后续的 17 个字节可以表示这个Code属性表的属性信息;

7、 操作数栈的最大深度(max_stack):占有2个字节,值为0x0001,表示栈帧中操作数栈的最大深度是1

8、局部变量表的最大容量(max_variable):占有2个字节,值为0x0001, JVM在调用该方法时,根据这个值设置栈帧中的局部变量表的大小;

9、 机器指令数目(code_length) :占有4个字节,值为0x0000 0005,表示后续的5 个字节 0x2A 、0xB7、 0x00、0x01、0xB1表示机器指令;

10、机器指令集(code[code_length]) :这里共有 5个字节,值为0x2A 、0xB7、 0x00、0x01、0xB1

11、显式异常表集合(exception_table_count): 占有2 个字节,值为0x0000,表示方法中没有需要处理的异常信息;

12、Code属性表的属性表集合(attribute_count): 占有2 个字节,值为0x0000,表示它没有其他的属性表集合,因为我们使用了-g:none 禁止编译器生成Code****属性表LineNumberTable 和LocalVariableTable;

解释下机器指令集:

第一个字节 **0x2A:查询Java 虚拟机规范中关于操作码的解释,0x2A 对应的操作是"aload_0",作用是将第一个引用类型局部变量推送至栈顶;

第二个字节 0xB7 :0xB7 对应的操作是:"invokespecial",作用是调用超类构造方法、实例初始化方法或私有方法;它****带有2个字节的参数,即后面的 0x00、0x01 是它的参数,这个参数是某个常量池中的索引,指向了常量池的第一项,该项表示一个方法引用项CONSTANT_Methodref_info结构体,表示java.lang.Object 类中的<init>()方法,即 java/lang/Object."<init>"

最新文章

  1. NERD_commenter——VIM批量注释与反注释插件
  2. 深度剖析C++对象池自动回收技术实现
  3. web后门top
  4. unknown software exception
  5. 【Android框架进阶〖0〗】ThinkAndroid注解机制
  6. C#实现自定义事件,用于监视变量变化
  7. Robotium自动化测试报告生成
  8. Android 之 MediaPlayer类
  9. UVA LA 7146 2014上海亚洲赛(贪心)
  10. Kotlin基本语法和使用
  11. [国嵌攻略][161][USB总线介绍]
  12. 【转】GPS静态观测网的设计指标
  13. Python_文本操作
  14. 使用FsCheck编写Property-based测试
  15. pythong+selenium自动创建随笔
  16. nginx配置location总结及rewrite规则写法(转)
  17. python学习第天14天。
  18. 戴尔poweredge r730服务器配置以及系统安装
  19. C# Atomic&lt;T&gt; Generic
  20. c# 1-2+3-4.....求和

热门文章

  1. 八爪鱼在哪里设置xpath
  2. java 通过HttpURLConnection与servlet通信
  3. 如何在ES5与ES6环境下处理函数默认参数
  4. SQL转化为MapReduce的过程
  5. jquery和ajax的关系详细介绍【转】
  6. DOM4J熟知
  7. composer安装laravel指定版本
  8. Collection、List、Set集合概括
  9. PAT1113: Integer Set Partition
  10. 使用EHCache需要注意的几个问题(转)