如何计算Class文件StackMapTable属性中的栈映射帧?
栈映射帧计算核心规则
StackMapTable是Java 7及以后版本为了加快字节码类型校验效率引入的属性,仅记录字节码中跳转目标位置、跳转指令下一条位置的局部变量表和操作数栈的类型状态,采用差异存储压缩体积,核心计算逻辑如下:
- 每个方法默认存在隐式初始帧,对应字节码偏移量0的位置,不需要写入StackMapTable:实例方法初始局部变量表第一个元素为
this,后续依次排列方法入参;静态方法初始局部变量表直接排列方法入参,所有初始帧的操作数栈为空。 - 帧的位置通过
offset_delta计算:若前一个帧对应字节码偏移量为prev_offset,当前帧对应偏移量 =prev_offset+offset_delta+ 1;若当前是StackMapTable的第一个帧,prev_offset默认取-1,计算结果等于offset_delta。 - 常用压缩帧类型:
append帧(frame_type 252~254):和前一帧相比操作数栈为空,局部变量表末尾新增了frame_type - 251个类型元素,仅存新增的类型即可。chop帧(frame_type 248~250):和前一帧相比操作数栈为空,局部变量表末尾砍掉了251 - frame_type个类型元素(注:long、double虽然占2个slot,但在局部变量类型列表里仅算1个元素),不需要存类型信息。
示例1 逐行验证
示例1是实例方法void foo(),无入参:
- 初始帧(偏移0):局部变量表为
[ this ],操作数栈为空。 - 字节码偏移5的
ifle 10是跳转指令,跳转目标是偏移10,所以需要在偏移10的位置记录栈映射帧。 - 计算偏移10位置的类型状态:
- 走跳转分支(i<=0):顺序执行到偏移5时,局部变量表已经存入int类型的i、j,变为
[ this, int, int ],操作数栈为空。 - 不走跳转分支(i>0):执行完偏移9的
istore_3(赋值k)后到偏移10,此时k已经超出if块作用域,slot3的类型无效,局部变量表有效部分还是[ this, int, int ],操作数栈为空。
- 走跳转分支(i<=0):顺序执行到偏移5时,局部变量表已经存入int类型的i、j,变为
- 差异对比:和初始帧相比,局部变量表新增了2个int类型,属于
append帧,frame_type = 251 + 2 = 253,offset_delta为10,和示例给出的StackMapTable内容完全一致。
示例2 逐行验证
示例2是静态方法static void chop(),无入参:
- 初始帧(偏移0):局部变量表为空,操作数栈为空。
- 跳转指令有两个:偏移5的
ifle 26(目标偏移26)、偏移11的ifne 23(目标偏移23),所以需要记录偏移23、偏移26两个位置的帧。 - 第一个帧(对应偏移23):
- 走到偏移23的两个路径,共同的有效局部变量为int i、int j、long k,局部变量表为
[ int, int, long ],操作数栈为空。 - 差异对比:和初始帧相比新增了3个类型元素,属于
append帧,frame_type = 251 + 3 = 254,offset_delta为23,和示例一致。
- 走到偏移23的两个路径,共同的有效局部变量为int i、int j、long k,局部变量表为
- 第二个帧(对应偏移26):
- 走到偏移26时,外层if块作用域结束,long类型的k已经无效,局部变量表回到
[ int, int ],操作数栈为空。 - 差异对比:和前一个帧(偏移23的帧)相比,局部变量表末尾砍掉了1个long类型元素,属于
chop帧,frame_type = 251 - 1 = 250,offset_delta为2,计算当前偏移量:23 + 2 + 1 = 26,完全匹配示例。
- 走到偏移26时,外层if块作用域结束,long类型的k已经无效,局部变量表回到
内容的提问来源于stack exchange,提问作者user5549139
相关产品推荐
相关产品推荐

