深入理解 JVM Class文件格式(九)
经过前八篇关于class文件的博客, 关于class文件格式的内容也基本上讲完了。 本文是关于class文件格式的最后一篇。 在这篇博客中, 将会讲解关于方法的几个属性。 理解这篇博客的内容, 对于理解JVM执行引擎起着重要作用。 关于虚拟机执行引擎有关的内容, 会在本专栏后面的博客中涉及。
在前面几篇博客中, 我们知道在class文件中描述一个方法, 会使用一个method_info 。 这个method_info中存放了方法的修饰符标志位,还引用了常量池中的项, 这些常量池数据项描述了在当前类中定义的某个方法的方法名, 方法的描述符。 关于这部分的内容, 请参考我之前的博客:深入理解JVM Class文件格式(七) 。
但是method_info中并没有存放方法的字节码, 也就是指令。 我们知道, 对于一个方法来说, 只要它不是抽象的(抽象类中的抽象方法或者接口中的方法), 那么肯定就会存在指令。 那么这些指令存放在哪里呢? 还有, 方法中的异常处理器(try-catch块)是如何在class文件中表述的? 方法声明抛出的异常是如何描述的呢? 如果你对这几个问题感兴趣, 或许你会在这篇博客中找到答案, 或者受到一些启发。
为了知识的连贯性, 我们首先简单回顾一下method_info的结构, 因为method_info与本文有着密切的关系。method_info 的结构如下:
Code属性
code属性是方法的一个最重要的属性。 因为它里面存放的是方法的字节码指令, 除此之外还存放了和操作数栈,局部变量相关的信息。 所有不是抽象的方法, 都必须在method_info中的attributes中有一个Code属性。下面是Code属性的结构, 为了更直观的展示Code属性和method_info的包含关系, 特意画出了method_info:
下面依次介绍code属性中的各个部分。
和上一篇博客中介绍的其他属性一样,attribute_name_index指向常量池中的一个CONSTANT_Utf8_info , 这个CONSTANT_Utf8_info 中存放的是当前属性的名字 “Code” 。
attribute_length给出了当前Code属性的长度(不包括前六字节)。
max_stack 指定当前方法被执行引擎执行的时候, 在栈帧中需要分配的操作数栈的大小。
max_locals指定当前方法被执行引擎执行的时候, 在栈帧中需要分配的局部表量表的大小。注意, 这个数字并不是局部变量的个数, 因为根据局部变量的作用域不同, 在执行到一个局部变量以外时, 下一个局部变量可以重用上一个局部变量的空间(每个局部变量在局部变量表中占用一个或两个Slot)。 方法中的局部变量包括方法的参数, 方法的默认参数this, 方法体中定义的变量, catch语句中的异常对象。 关于执行引擎的相关内容会在后面的博客中讲到。
code_length指定该方法的字节码的长度, class文件中每条字节码占一个字节。
code存放字节码指令本身, 它的长度是code_length个字节。
exception_table_length 指定异常表的大小
exception_table就是所谓的异常表, 它是对方法体中try-catch_finally的描述。 exception_table可以看做是一个数组, 每个数组项是一个exception_info结构, 一般来说每个catch块对应一个exception_info,编译器也可能会为当前方法生成一些exception_info。 exception_info的结构如下(为了直观的显示exception_info, exception_table和Code属性的关系, 画出了Code属性的话读者就会更清楚各个数据项之间的位置关系和包含关系):
下面讲解exception_info中的各个字段的意思。
start_pc是从字节码(Code属性中的code部分)起始处到当前异常处理器起始处的偏移量。
end_pc是从字节码起始处到当前异常处理器末尾的偏移量。
handler_pc是指当前异常处理器用来处理异常(即catch块)的第一条指令相对于字节码开始处的偏移量。
catch_type是一个常量池索引, 指向常量池中的一个CONSTANT_Class_info数据项, 该数据项描述了catch块中的异常的类型信息。这个类型必须是java.lang.Throwable的或其子类。
所以可以总结, 一个异常处理器(exception_info)的意思是: 如果偏移量从start_pc到end_pc之间的字节码出现了catch_type描述的类型的异常, 那么就跳转到偏移量为handler_pc的字节码处去执行。如果catch_type为0, 就代表不引用任何常量池项(再回顾一下, 常量池中的项是从1开始计的), 那么这个exception_info用于实现finally子句。
我们一直在介绍Code属性, 只不过刚才进行了一个小插曲, 介绍了Code属性中的exception_table中的exception_info的详细信息。 下面我们继续介绍Code 属性中的其他信息, 希望读者不要被绕晕了 : )
attributes_count 表示当前Code 属性中存在的其他属性的个数。 现在我们知道, class中的属性, 不仅会出现在顶层的class中, 会存在field_info中, 会存在method_info中, 甚至还会出现在属性中。
attributes可以看做是一个数组, 里面存放了Code属性中的其他属性。 Code 属性中可以出现的其他属性有LineNumberTable和LocalVariableTable 。 这两个属性会在下面介绍。
LineNumberTable属性
LineNumberTable属性存在于Code属性中, 它建立了字节码偏移量到源代码行号之间的联系。 这个属性是可选的, 编译器可以选择不生成该属性。下面是该属性的结构(同样给出了全局的位置关系,LineNumberTable在图的右下角部分):
由于这个属性并不是重点, 我们在此简单的讲述。
每个LineNumberTable中的line_number_table部分, 可以看做是一个数组, 数组的每项是一个line_number_info , 每个line_number_info 结构描述了一条字节码和源码行号的对应关系。 其中start_pc是这个line_number_info 描述的字节码指令的偏移量, line_number是这个line_number_info 描述的字节码指令对应的源码中的行号。可以看出, 方法中的每条字节码都对应一个line_number_info , 这些line_number_info 中的line_number可以指向相同的行号, 因为一行源码可以编译出多条字节码。
LocalVariableTable属性
LocalVariableTable 属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。 这个属性存在于Code属性中。 这个属性是可选的, 编译器可以选择不生成这个属性。该属性的结构如下:(同样给出了全局的位置关系图,LocalVariableTable 在该图的右下角 )
由于这个属性相对不那么重要, 这里只是大概讲解一下。
每个LocalVariableTable 的local_variable_table部分可以看做是一个数组, 每个数组项是一个叫做local_variable_info的结构, 该结构描述了某个局部变量的变量名和描述符, 还有和源代码的对应关系。下面讲解local_variable_info的各个部分:
start_pc是当前local_variable_info所对应的局部变量的作用域的起始字节码偏移量;
length是当前local_variable_info所对应的局部变量的作用域的大小。 也就是从字节码偏移量start_pc 到start_pc+length就是当前局部变量的作用域范围;
name_index指向常量池中的一个CONSTANT_Utf8_info, 该CONSTANT_Utf8_info描述了当前局部变量的变量名;
descriptor_index指向常量池中的一个CONSTANT_Utf8_info, 该CONSTANT_Utf8_info描述了当前局部变量的描述符;
index描述了在该方法被执行时,当前局部变量在栈帧中局部变量表中的位置。
由此可知, 方法中的每个局部变量都会对应一个local_variable_info 。
Exceptions属性
首先需要说明, Exceptions属性不是存在于Code属性中的, 它存在于method_info中的attributes中。 和Code属性是平级的。 这个属性描述的是方法声明的可能会抛出的异常, 也就是方法定义后面的throws声明的异常列表, 请不要和上面提到的异常处理器混淆。 异常处理器描述了方法的字节码如何处理异常, 而Exceptions属性描述方法可能会抛出哪些以异常。下面讲解Exceptions属性的结构(左下角为Exceptions属性):
下面讲解Exceptions属性中的信息。
attribute_name_index和attribute_length就不多说了, 和其他属性是一样的。
number_of_exceptions是该方法要抛出的异常的个数。
exceptions_index_table可以看做一个数组, 这个数组中的每一项占两个字节, 这两个字节是对常量池的索引, 它指向一个常量池中的CONSTANT_Class_info。 这个CONSTANT_Class_info描述了一个被抛出的异常的类型。
总结
到此为止, 和方法相关的属性就介绍完了。 这篇博客讲解的内容相对比较复杂。 下面以一个实例进行验证, 实例代码:
package com.jg.zhang;public class Test {public void test() throws Exception{int localVar = 0;try{Class.forName("com.jg.zhang.Person");}catch(ClassNotFoundException e){throw e;}finally{System.out.println(localVar);}}
}
反编译后的test方法部分(省略了常量池等信息):
public void test() throws java.lang.Exception;flags: ACC_PUBLICExceptions:throws java.lang.ExceptionCode:stack=2, locals=4, args_size=10: iconst_01: istore_12: ldc #18 // String com.jg.zhang.Person4: invokestatic #20 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;7: pop8: goto 2411: astore_212: aload_213: athrow14: astore_315: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;18: iload_119: invokevirtual #32 // Method java/io/PrintStream.println:(I)V22: aload_323: athrow24: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;27: iload_128: invokevirtual #32 // Method java/io/PrintStream.println:(I)V31: returnException table:from to target type2 8 11 Class java/lang/ClassNotFoundException2 14 14 anyLineNumberTable:line 7: 0line 11: 2line 13: 8line 15: 12line 16: 14line 17: 15line 18: 22line 17: 24line 20: 31LocalVariableTable:Start Length Slot Name Signature0 32 0 this Lcom/jg/zhang/Test;2 30 1 localVar I12 2 2 e Ljava/lang/ClassNotFoundException;
结合上面的讲解和图解, 再分析反编译的结果, 就一目了然了: 所有的结果是一个method_info, method_info开始处是访问标志信息。 然后是method_info的 Exceptions属性 , Exceptions属性属性下面是Code属性, Code属性中又包括字节码, 异常处理器 ,LineNumberTable属性和LocalVariableTable 属性。
由于这篇博客讲解的内容大多和方法有关, 所以会直接或间接的和method_info有联系, 最后给出一张全局图, 这样的话, 读者就比较明确, 一个完整的方法, 是如何在class文件中描述的,由于考虑到复杂性, 这些属性或其他数据项中, 对常量池的引用均未画出:
深入理解 JVM Class文件格式(九)相关推荐
- 深入理解 JVM Class文件格式(八)
在本专栏的第一篇文章 深入理解Java虚拟机到底是什么 中, 我们主要讲解了什么是虚拟机, 这篇博客是对JVM的一个概述. 在随后的几篇文章中,一直在讲解class文件格式. 在今天这篇博客中, 将会 ...
- 深入理解 JVM Class文件格式(七)
本专栏列前面的一系列博客, 对Class文件中的一部分数据项进行了介绍. 本文将会继续介绍class文件中未讲解的信息. 先回顾一下上面一篇文章. 在上一篇博客中, 我们介绍了: this_class ...
- 深入理解 JVM Class文件格式(五)
(8) CONSTANT_Class_info 常量池中的一个CONSTANT_Class_info, 可以看做是CONSTANT_Class数据类型的一个实例. 他是对类或者接口的符号引用. 它描述 ...
- 深入理解 JVM Class文件格式(三)
** JVM常量池中各数据项类型详解 ** 关于常量池的大概内容, 已经在 深入理解 JVM Class文件格式(一) 中讲解过了, 这篇文章中还介绍了常量池中的11种数据类型. 本文的任务是详细讲解 ...
- 深入理解 JVM Class文件格式(十)
到此, 所有关于class文件格式的重要内容都已经讲解完了, 不敢说面面俱到, 但是敢说大部分重要的内容都包含在内了.前前后后用了9篇博客来专门讲解class文件结构, 为什么花那么多的时间和精力来介 ...
- 深入理解 JVM Class文件格式(六)
经过前几篇文章, 终于将常量池介绍完了, 之所以花这么大的功夫介绍常量池, 是因为对于理解class文件格式,常量池是必须要了解的, 因为class文件中其他地方,大量引用了常量池中的数据项. 对于还 ...
- 深入理解JVM类文件格式
我们知道Java最有名的宣传口号就是:"一次编写,到处运行(Write Once,Run Anywhere)",而其平台无关性则是依赖于JVM, 所有的java文件都被编译成字节码 ...
- 深入理解 JVM Class文件格式(二)
** class文件中的特殊字符串 ** 特殊字符串是常量池中符号引用的一部分,包括三种: 类的全限定名, 字段和方法的描述符, 特殊方法的方法名. 下面我们就分别介绍这三种特殊字符串. (1) 类的 ...
- 深入理解 JVM Class文件格式(一)
** 一.JVM体系结构 ** ** 二.class格式文件概述 ** class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得clas ...
最新文章
- 面试官:谈谈JWT鉴权的应用场景及使用建议?
- 计算机是怎样知道屏幕位置的,如何看自己的电脑显示器是多少寸
- 深化对KMP算法的理解
- 操作篇 ###rip协议学习和理解##
- Linux qt程序打包依赖库,Linux打包免安装的Qt程序(编写导出依赖包的脚本copylib.sh,程序启动脚本MyApp.sh)...
- win和mac系统Sublime Text 3配置编译c和c++
- MIMO-OTFS in High-Doppler Fading Channels:Signal Detection and Channel Estimation(3)
- 最简单的正交试验教程
- python获取图像灰度极值点_【图像处理】灰度图、亮度峰值极值查找
- 聊一聊为什么JAVA只允许单继承
- Unity 2D打地鼠小游戏
- PM notifier
- 1024*1024像素的图片,文件大小多少?
- 软件工程微信平台作业总结
- ORB_VI思想框架
- 数字图像处理学习笔记(七)——用Pycharm及MATLAB实现三种图像内插法(最近邻内插法、双线性内插法、双三次内插法)
- 《前端》权限链接--vue前端权限控制方案详解附demo_feiyu_may的博客-CSDN博客_vue 前端权限
- [NOIP2009]靶形数独
- [编程题]Shopee的零食柜:二分法(情景题,理解题意很重要)
- 计算机毕业设计Java银杏湖景区旅游管理信息平台(源码+系统+mysql数据库+Lw文档)
热门文章
- [python opencv 计算机视觉零基础到实战] 十、图片效果毛玻璃
- android 传感器 balance filter,Android 传感器 API.doc
- vue表格刷新数据_Vue.js+Layer表格数据绑定与实现更新的实例
- 看着女朋友的肚子,以肉眼可见的速度大起来......
- 每日一笑 | 为什么椅子总是最乱的?
- 据说这是史上最牛逼的可视化神器
- mysql双机互备linux成功的_配置MySQL双机热备 - Linux服务器MySQL双机热备份试验_数据库技术_Linux公社-Linux系统门户网站...
- android fragmentstatepageradapter框架,安卓爬坑指南之FragmentStatePagerAdapter
- php调用另一个php文件里的变量的值,thinkphp中一个方法调用另一个步骤的变量
- Java JSON对象怎么遍历_Java遍历JsonObject对象