管中窥探java虚拟机(一)

认识 java虚拟机字节码的StackMapTable属性

背景

如果分析过class文件,如果写过简易的java虚拟机.也许你会遇到这两个问题:
1. 打印java方法的指令与javap显示的有所差异.
2. 虚拟机不受控制的运行.
但是class文件解析却没有问题,那么这个时候也许应该重新翻翻Java虚拟机规范(JavaSE7)了.

引题

我们知道java源文件经过编译后,生成.class文件,关于.class文件的结构定义在java虚拟机规范中有详细的描述。这里贴一张图,大致了解.class文件结构,具体每个字段的详细细节请参阅java虚拟机规范。

.class文件结构图

216行的method_info,存放的就是java中方法的信息,例如方法的访问权限,方法名,参数,返回值等等。
当然最重要的就是java虚拟机的指令。method_info有很多属性来描述自己,其中code属性记录指令的相关信息。
而code属性也有很多属性描述自己,stackMapTable属性就是code属性的一个属性。

好了,到这里算是引入了话题,明白了stackMapTable是怎么一个概念了。

java虚拟机指令格式

我们知道java虚拟机是基于栈的, 也就是说java虚拟机的指令都是对栈操作的,我们之前学过的x86汇编都是针对寄存器的。例如一个加法运算,
x86汇编可能就是

    ADD AX, BX 

表示将BX寄存器的数据和AX寄存器的数据相加,结果放入AX寄存器中,

JVM指令可能就是

    iadd

表示从操作数栈顶弹出两个整数,相加,将结果压栈。
所以JVM指令很多都是只有操作码,没有操作数的。
剩下很少的JVM指令格式是 opcode operand1 operand2 每个占一个字节, operand1 << 16 + operand2 是opcode的操作数.
还有个别特殊的指令, 例如switch, breakpoint等等, 这里暂不介绍了, 有机会专门对虚拟机指令按功能分类和操作数长度分类做个笔记.

近距离看看虚拟机指令

为什么会关注stackMapTable属性呢? 如果你不care jvm的原理和执行流程,那也确实没有必要关注了。如果你对jvm运行.class文件的实现有强烈的好奇心,或者你正在尝试实现一个java虚拟机,你会发现有这么一个现象:
用java1.6 (1.6以上版本都行,stackMapTable属性是1.6之后版本加入的)编译这么一段java文件

public class Hello {
    public int add(int a, int b) {
        if (a > 10) {
            return 1;
        }
        return 2;
    }
}

输入javac Hello.java 编译java源文件.
输入javap -v Hello 查看方法的指令流.
add方法指令

重点关注标号为0 ~ 8的指令
当我们自己按照java虚拟机规范解析class文件时,会发现某些指令和javap -v 显示的不一样.
截屏看看解析的指令流
自己实现的javap
请忽略Args_size 和 LineNumberTable, 不是问题的原因.
请看标号为3的指令, 操作码为if_icmple, javap显示的是8, 而显示的是5.
曾经也多次怀疑自己解析出了错误. 奈何苦寻无果,只好看看Hello.class文件, 通过debug可以算出这条指令位于第229,230,231三个字节位置,其中第229个字节是操作码if_icmple, 找到第230, 231两个字节.
Hello.class字节码
十进制230转成十六进制就是0xE6, 看看0xE6,0xE7位置, 是00005
注意, class文件是用大端保存的, 也就是说这两个字节就是5, 证明解析class文件没错.

StackMapTable是什么

看看javap 输出的结果, 细心的你也许会发现,LineNumberTable下面还有一行 StackMapTable ,frame_type = 8, 更巧的是第3行指令后面的参数就是8.
问题的原因就在这里,是Code属性的StackMapTable属性在作怪.StackMapTable 是java1.6之后引入的一种机制,目的是描述局部变量表和操作数栈的字节码偏移,支持新的java字节码检验算法.
官方解释是这样的:
StackMapTable属性是一个变长属性,位于Code属性的属性表中.这个属性会在虚拟机类加载的类型阶段被使用.
StackMapTable属性包含0至多个栈映射帧(Stack Map Frames), 每个栈映射帧都显式或者隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型(Verification Types).

详细介绍请看截图
StackMapTable

注意:
一个Code属性对应一个唯一的java方法, 如果方法是abstract或者native类型,则不能有确定的Code属性.每个Code属性最多只能有一个StackMapTable属性.如果Code属性没有StackMapTable属性,则视为有一个默认的StackMapTable, 该StackMapTable的stack map frame个数为0.

回顾总结

到这里,我们之前遇到的问题就知道原因所在了.如果你编译其他的java文件,会发现一些方法的Code属性并没有StackMapTable属性,或者说是隐式的StackMapTable属性.这是为什么呢?
因为该属性是和指令相关的,根据官方解释该属性作用是描述局部变量表和操作数栈的字节码偏移,也就是说如果某指令需要用到局部变量表,那么才有可能有StackMapTable属性.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值