JVM StackMapTable 属性的作用及理解

编程技术  /  houtizong 发布于 2年前   147

 

      Java 6版本之后JVM引入了栈图(Stack Map Table)概念。为了提高验证过程的效率,在字节码规范中添加了Stack Map Table属性,以下简称栈图,其方法的code属性中存储了局部变量和操作数的类型验证以及字节码的偏移量。也就是一个method需要且仅对应一个Stack Map Table。在Java 7版本之后把栈图作为字节码文件中的强制部分。 本来程序员是不需要关心JVM 中的JIT编译器的细节,也不用知道编译原理或者数据流、控制流的细节。但栈图强制了,如果要生成bytecode,必须准确知道每个字节码指令对应的局部变量和操作数栈的类型。这是因为Java7在编译的时期做了一些验证期间要做的事情,那就是类型检查,也就是栈图包含的内容。

     想想都比较抓狂,但是JVM 做的这一点点性能优化对整体性能提升也没起到什么卵用。Java的验证在类加载的时候只会运行一次,而占据了大部分时间的操作是IO的消耗,而不是验证过程。即使现在有了栈图,验证过程依然会执行,栈图的存在只是节省了一部分的验证时间。并且JVM的设计者还必须兼容没有栈图的验证的实现,因为Java7以前版本是没有强制栈图这个概念的,然而Java8 依然延续了栈图的字节码结构。

     下面来结合一个例子看一下栈图的结构。

   Java代码如下:

package bytecode; /** * Created by yunshen.ljy on 2015/6/16. */public class Coffee {     int bean;     public void getBean(int var) {        if (var > 0) {            this.bean = var;        } else {            throw new IllegalArgumentException();        }    } }

 

 

     来看一下getBean方法中的字节码,然后重点看StackMapTable,栈图包含了两个entry

public void getBean(int);  Code:   Stack=2, Locals=2, Args_size=2   0:        iload_1   1:        ifle    12   4:        aload_0   5:        iload_1   6:        putfield    #2; //Field bean:I   9:        goto 20   12:      new #3; //class java/lang/IllegalArgumentException   15:      dup   16:      invokespecial   #4; //Method java/lang/IllegalArgumentException."<init>":()V   19:      athrow   20:      return  LineNumberTable:   line 11: 0   line 12: 4   line 14: 12   line 16: 20   StackMapTable: number_of_entries = 2   frame_type = 12 /* same */   frame_type = 7 /* same */

 

 

       最后三行,看到了StackMapTable,没错这个就是栈图。StackMapTable包含了attribute_name_indexattribute_lengthnumber_of_entries以及entries结构。其中number_of_entries代表了栈图frame的个数。在这里我们的entries  frame_type = 12 /* same */ frame_type = 7 /* same */。每一个entry元素都代表了一个方法的StackMapFrame。其包含了字节码的偏移量和局部变量表、操作数栈的验证类型,所以entry的顺序是很重要的。其实第一个StackMapFrame是隐式的,并且是通过类型检查器的方法描述计算出来。这里我们看到的frame_type = 12 /* same */ 其实是方法的第二个StackMapFrame,只不过是显示的StackMapFrame

     这里先补充一点字节码指令和参数概念,字节码的指令,是由一个字节长度的助记符表示的操作码(Opcode)以及其随后的需要操作的若干参数构成。有的指令并不一定需要参数。但这里注意不要混淆一个概念,这里的参数和操作数(oprends)不是同一个概念。这里的arguments(参数)是静态的值,编译期就存储在编译后的字节码中,而Oprends(操作数)的值第一节介绍的操作数栈中运行期才知道值的数据结构。不知道讲清楚没有,但发现很多译文以及文章都会混淆指令集的“参数”和操作数栈的“操作数”。其实参数是以一个字节为单位的有符号整型,用于指向跳转目标地址,如果是超过一个字节,就以两个参数存储,两个参数还是依照高位在前的方式存储。 如:目标指令地址 = goto指令地址 + ( 参数1 << 8 | 参数2 )

     所以这里的偏移量要这样计算,在本例中frame_type =12 ,这里12 是这一个frame的字节码偏移量,即offset_delta。下一个StackMapFrame的偏移量是offset_delta+1。在本例中是7+1=8。所以我们看到ifle  12,字节码指令的参数是12,所以entries中第一个StackMapFrameoffset_delta=12,同理 goto 20 其实是goto 12+8entries第二个StackMapFrameoffset_delta=8。所以StackMapTable通过记录偏移量来保证字节序,并且不会重复记录。

    本例中的StackMapFrame frame_type /* same */项表示当前帧和前一帧有相同的局部变量,并且当前操作数栈为空。对于当前局部变量表和操作数栈的数据流可以参考之前的一篇例子。http://yunshen0909.iteye.com/admin/blogs/2221144 。本文主要结合JVM 8规范,如有错误请指正。

 

 

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客