【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合
深入理解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底层而言,它能表示的机器操作码不多于2的 8 次方,即 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_PUBLIC
、ACC_STATIC
、ACC_SYNCHRONIZED
、ACC_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",作用是将第一个引用类型局部变量推送至栈顶;
深入理解css中position属性及z-index属性 在网页设计中,position属性的使用是非常重要的.有时如果不能认识清楚这个属性,将会给我们带来很多意想不到的困难. position属性共 ... 深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ... 深入理解css中position属性及z-index属性 请看出处:https://www.cnblogs.com/zhuzhenwei918/p/6112034.html 在网页设计中,positi ... 形参和实参 我们知道,在Java中定义方法时,是可以定义参数的,比如: public static void main(String[] args){ } 这里的args就是一个字符串数组类型的参数. ... 1.声明文档的字符编码 <meta charset='utf-8'> 2.声明文档的兼容模式 <meta http-equiv="X-UA-Compatible" ... 一.为什么要写这篇文章 今天看到一个问题: 两个div 都设置 display:inline-block,正常显示:但是在第二个div中加一个块级元素或者内联元素,显示就变了个样,为什么? <m ... 我们把描述JavaScript中定义内部特性的属性叫做属性描述符 分为两大类:数据描述符和存取描述符 数据描述符是一个拥有可写或不可写的属性(Writable); 存取描述符不包含数据值,是一组拥有g ... function F(){}; var f = new F(); f.name = "cf"; f.hasOwnProperty("name");//true ... 首先,$listeners是什么? 假设有父组件Parent和子组件Child // Parent <template> ... <child v-on:event-one=&quo ... public sealed class SAFCFormater { /// <summary> /// 文本格式到HTML /// </summary> /// <pa ... 一.写在前面 要求做一个,后台发布信息,前台能即时得到通知的消息推送功能.网上搜了也有很多方式,ajax的定时询问,Comet方式,Server-Sent方式,以及websocket.表示除了定时询问 ... 跟着人工智能/机械学习的兴起,Python再次取得广泛程序员的关注.而JetBrains出品的PyCharm无疑是最好用的Python IDE之一. 把本身的IDE装备安排得既有逼格又雅观,这是每个P ... 摘要: 开发者开发部署web应用时通常使用tomcat服务器,很多初学者只懂得在开发工具上配置,但离开了开发工具,自己手动配置部署,并让一个项目跑起来,你会了吗.小编也遇到过这样的困扰.网上查找的资料 ... android studio中使用SlidingMenu: 超简单Android Studio导入第三方库(SlidingMenu)教程绝对傻瓜版 android 更新sdk23以后,报错提示Floa ... 还是以第一篇博客中给出的例子,根据代码实例来入手分析. static { InputStream inputStream = MybatisTest.class.getClassLoader().ge ... 环境: windows server 2008 R2 rabbitmq 3.7.2 下载插件: http://www.rabbitmq.com/community-plugins.html 注意要下载 ... Python学习 Part7:类 1. 作用域和命名空间 命名空间(namespace)就是一个从名称到对象的映射. 命名空间的一些实例:内置名称集(函数,像abs(),和内置异常名称),一个模块中的 ... 在网站开发中,有时候需要获取浏览器滚动条的宽度,在武汉蚂蹄软件服务中心的技术人员指导之下,我实现了该需求.记录如下: 首先说明一下原理: ①生成一个div,设置滚动条不可见,记录其宽度: ②将上面的d ... 欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文将帮助您使用基于HBase的Apache Spark Streaming.Spark Streaming是Spark API核心的一个扩 ...第二个字节 0xB7
:0xB7 对应的操作是:"invokespecial",作用是调用超类构造方法、实例初始化方法或私有方法;它****带有2个字节的参数,即后面的 0x00、0x01 是它的参数,这个参数是某个常量池中的索引,指向了常量池的第一项,该项表示一个方法引用项CONSTANT_Methodref_info结构体,表示java.lang.Object 类中的<init>()方法,即 java/lang/Object."<init>"
【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合的更多相关文章
随机推荐