java虚拟机规范(se8)——class文件格式(六)
4.7.4 StackMapTable 属性
StackMapTable 属性是一个变长属性,位于 Code(§4.7.3)属性的属性表中。这个属性会在虚拟机类加载的类型阶段(§4.10.1)被使用。
StackMapTable 属性包含 0 至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型(Verification Types §4.10.1)。
类型检测器(Type Checker)会检查和处理目标方法的局部变量和操作数栈所需要的类型。本章节中,一个存储单元(Location)的含义是唯一的局部变量或操作数栈项。
我们还将用到术语“栈映射帧”(Stack Map Frame)和“类型状态”(Type State)来描述如何从方法的局部变量和操作数栈的存储单元映射到验证类型(Verification Types)。 当描述 Class 文件侧的映射时,我们通常使用的术语是“栈映射帧”,而当描述类型检查器侧的映射关系时,我们通常使用的术语是“类型状态”。
在版本号大于或等于 50.0 的 Class 文件中,如果方法的 Code 属性中没有附带StackMapTable 属性,那就意味着它带有一个隐式的 StackMap 属性。这个 StackMap 属性的作用等同于 number_of_entries 值为 0 的 StackMapTable 属性。一个方法的 Code 属性最多只能有一个 StackMapTable 属性,否则将抛出 ClassFormatError 异常。
StackMapTable 属性的格式如下:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
StackMapTable 结构项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的有效索引,常量池在该索引的项处必须是CONSTANT_Utf8_info(§4.4.7)结构,表示“StackMapTable”字符串。
attribute_length
attribute_length 项的值表示当前属性的长度,不包括开始的 6 个字节。
number_of_entries
number_of_entries 项的值给出了 entries 表中的成员数量。Entries 表的每个成员是都是一个 stack_map_frame 结构的项。
entries[]
entries 表给出了当前方法所需的 stack_map_frame 结构。
每个 stack_map_frame 结构都使用一个特定的字节偏移量来表示类型状态。每个帧类型(Frame Type)都显式或隐式地标明一个 offset_delta(增量偏移量)值,用于计算每个帧在运行时的实际字节码偏移量。使用时帧的字节偏移量计算方法为:前一帧的字节码偏移量(Bytecode Offset)加上 offset_delta 的值再加 1,如果前一个帧是方法的初始帧
(Initial Frame),那这时候字节码偏移量就是 offset_delta。
只要保证栈映射帧有正确的存储顺序,在类型检查时我们就可以使用增量偏移量而不是实际的
字节码偏移量。此外,由于堆每一个帧都使用了 offset_delta+1 的计算方式,我们可以确保偏
移量不会重复。
在 Code 属性的 code[]数组项中,如果偏移量 i 的位置是某条指令的起点,同时这个 Code属性包含有 StackMapTable 属性,它的 entries 项中也有一个适用于地址偏移量 i 的stack_map_frame 结构,那我们就说这条指令拥有一个与之相对应的栈映射帧。
stack_map_frame 结构的第一个字节作为类型标记(Tag),第一个字节后会跟随 0 或多个字节用于说明更多信息,这些信息因类型标记的不同而变化。
一个栈映射帧可以包含若干种帧类型(Frame Types):
union stack_map_frame {
same_frame;
same_locals_1_stack_item_frame;
same_locals_1_stack_item_frame_extended;
chop_frame;
same_frame_extended;
append_frame; full_frame;
}
所有的帧类型,包括 full_frame,它们的部分语义会依赖于前置帧,这点使得确定基准帧(Very First Frame)变得尤为重要,方法的初始帧是隐式的,它通过方法描述符计算得出,详细信息请参考methodInitialStackFrame(§4.10.1.3.3)。
帧类型 same_frame 的类型标记(frame_type)的取值范围是 0 至 63,如果类型标记所确定的帧类型是 same_frame 类型,则明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,并且对应的 stack 项的成员个数为 0。当前帧的 offset_delta 值就使用 frame_type 项的值来表示。
same_frame {
u1 frame_type = SAME; /* 0-63 */
}
帧类型 same_locals_1_stack_item_frame 的类型标记的取值范围是 64 至 127。如果类型标记所确定的帧类型是 same_locals_1_stack_item_frame 类型,则说明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,同时对应的 stack[]数组的成员个数为 1。当前帧的 offset_delta 值为 frame_type-64。并且有一个 verification_type_info 项跟随在
此帧类型之后,用于表示那一个 stack 项的成员。
same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
verification_type_info stack[1];
}
范围在 128 至 246 的类型标记值是为未来使用而预留的。
帧类型 same_locals_1_stack_item_frame_extended 由值为 247 的类型标记表示,它表明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,同时对应的 stack[]数组的成员个数为 1。当前帧的 offset_delta 的值需要由 offset_delta 项明确指定。有一个 stack[]数组的成员跟随在 offset_delta 项之后。
same_locals_1_stack_item_frame_extended {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
u2 offset_delta; verification_type_info stack[1];
}
帧类型 chop_frame 的类型标记的取值范围是 248 至 250。如果类型标记所确定的帧类型是为 chop_frame,则说明对应的操作数栈为空,并且拥有和前一个栈映射帧相同的 locals[]数组,不过其中的第 k 个之后的 locals 项是不存在的。k 的值由 251-frame_type 确定。
chop_frame {
u1 frame_type = CHOP; /* 248-250 */
u2 offset_delta;
}
帧类型 same_frame_extended 由值为 251 的类型标记表示。如果类型标记所确定的帧类型是 same_frame_extended 类型,则说明当前帧有拥有和前一个栈映射帧的完全相同的locals[]数组,同时对应的 stack[]数组的成员数量为 0。
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
u2 offset_delta;
}
帧类型 append_frame 的类型标记的取值范围是 252 至 254。如果类型标记所确定的帧类型为 append_frame,则说明对应操作数栈为空,并且包含和前一个栈映射帧相同的 locals[]数组,不过还额外附加 k 个的 locals 项。k 值为 frame_type-251。
append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_delta;
verification_type_info locals[frame_type - 251];
}
在 locals[]数组中,索引为 0 的(第一个)成员表示第一个添加的局部变量。如果要从条件“locals[M]表示第 N 个局部变量”中推导出结论“locals[M+1]就表示第 N+1 个局部变量”的话,那就意味着 locals[M]一定是下列结构之一:
Top_variable_info
Integer_variable_info
Float_variable_info
Null_variable_info
UninitializedThis_variable_info
Object_variable_info
Uninitialized_variable_info
否则,locals[M+1]就将表示第 N+2 个局部变量。对于任意的索引 i,locals[i]所表示的局部变量的索引都不能大于此方法的局部变量表的最大索引值。
在 stack[]数组中,索引为 0 的(第一个)成员表示操作数栈的最底部的元素,之后的成员依次靠近操作数栈的顶部。操作数栈栈底的元素对应的索引为 0,我们称之为元素 0,之后元素依次是元素 1、元素 2 等。如果要从条件“stack[M]表示第 N 个元素”中推导出结论“stack[M+1]表示第 N+1 个元素”的话,那就意味着 stack[M]一定是下列结构之一:
Top_variable_info
Integer_variable_info
Float_variable_info
Null_variable_info
UninitializedThis_variable_info
Object_variable_info
Uninitialized_variable_info
否则,stack[M+1]将表示第 N+2 个元素,对于任意的索引 i,stack[i]所表示的栈元素索
引都不能大于此方法的操作数的最大深度。
verification_type_info 结构的第一个字节 tag 作为类型标记,之后跟随 0 至多个字节表示由 tag 类型所决定的信息。每个 verification_type_info 结构可以描述 1 个至 2 个存储单元的验证类型信息。
union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
Object_variable_info;
Uninitialized_variable_info;
}
Top_variable_info 类型说明这个局部变量拥有验证类型 top(ᴛ)。
Top_variable_info {
u1 tag = ITEM_Top; /* 0 */
}
Integer_variable_info 类型说明这个局部变量包含验证类型 int
Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */
}
Float_variable_info 类型说明局部变量包含验证类型 float
Float_variable_info {
u1 tag = ITEM_Float; /* 2 */
}
Long_variable_info 类型说明存储单元包含验证类型 long,如果存储单元是局部变量,则要求:
不能是最大索引值的局部变量。
按顺序计数的下一个局部变量包含验证类型 ᴛ
如果单元存储是操作数栈成员,则要求:
当前的存储单元不能在栈顶。
靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
Long_variable_info 结构在局部变量表或操作数栈中占用 2 个存储单元。
Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
}
Double_variable_info 类型说明存储单元包含验证类型 double。如果存储单元是局部变量,则要求:
不能是最大索引值的局部变量。
按顺序计数的下一个局部变量包含验证类型 ᴛ
如果单元存储是操作数栈成员,则要求:
当前的存储单元不能在栈顶。
靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
Double_variable_info 结构在局部变量表或操作数栈中占用 2 个存储单元。
Double_variable_info {
u1 tag = ITEM_Double; /* 3 */
}
Null_variable_info 类型说明存储单元包含验证类型 null。
Null_variable_info {
u1 tag = ITEM_Null; /* 5 */
}
UninitializedThis_variable_info 类型说明存储单元包含验证类型uninitializedThis。
UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */
}
Object_variable_info 类型说明存储单元包含某个 Class 的实例。由常量池在cpool_index 给出的索引处的 CONSTANT_CLASS_Info(§4.4.1)结构表示。
Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}
Uninitialized_variable_info 说明存储单元包含验证类型
uninitialized(offset)。offset 项给出了一个偏移量,表示在包含此 StackMapTable 属性的 Code 属性中,new 指令创建的对象所存储的位置。
Uninitialized_variable_info {
u1 tag = ITEM_Uninitialized /* 8 */
u2 offset;
}
java虚拟机规范(se8)——class文件格式(六)的更多相关文章
- java虚拟机规范(se8)——class文件格式(四)
4.7 属性 属性用于class文件格式中的ClassFile,field_info,method_info和Code_attribute结构. 所有的属性都是下面的格式: attribute_inf ...
- java虚拟机规范(se8)——class文件格式(一)
第四章 class文件格式 本章介绍了java虚拟机的class文件格式.每一个class文件包含一个单独的类或者接口的定义.虽然类和接口不一定都定义在文件中(比如类和接口亦可以通过类加载器直接生成) ...
- java虚拟机规范(se8)——class文件格式(五)
4.7.1 定义和命名新属性 允许编译器定义和发布的class文件在class文件结构体.field_info结构体.method_info结构体和Code结构体中的attributes表中包含新的属 ...
- java虚拟机规范(se8)——class文件格式(三)
4.5 字段 字段使用field_info结构来描述. 在同一个class文件中的两个字段不能有相同的名称和描述符. 结构的格式如下: field_info { u2 access_flags; u2 ...
- java虚拟机规范(se8)——class文件格式(二)
4.4 常量池 java虚拟机指令并不依赖类.接口.类实例或者数组的运行时布局.相反,指令依靠常量池中的符号信息. 所有的常量池条目都有如下的通用结构: cp_info { u1 tag; u1 in ...
- java虚拟机规范(se8)——class文件格式(七)
4.7.5 Exceptions 属性 Exceptions 属性是一个变长属性,它位于 method_info(§4.6)结构的属性表中. Exceptions 属性指出了一个方法需要检查的可能抛出 ...
- java虚拟机规范(se8)——java虚拟机结构(一)
本文翻译自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 第二章 虚拟机结构 本文档描述了一个抽象的虚拟机规范,并不描述 ...
- java虚拟机规范(se8)——java虚拟机结构(六)
2.11 指令集简介 java虚拟机指令由一个字节的操作码,接着时0个或多个操作数组成,操作码描述了执行的操作,操作数提供了操作所需的参数或者数据.许多指令没有操作数只包含一个操作码. 如果忽略异常处 ...
- java虚拟机规范(se8)——java虚拟机的编译(四)
3.12 抛出和处理异常 在程序中使用throw关键字来抛出异常.编译结果很简单. void cantBeZero(int i) throws TestExc { if (i == 0) { thro ...
随机推荐
- nginx+lua+redis实现灰度发布_test
nginx+lua+redis实现灰度发布: 灰度发布是指在黑白之间能够平滑过渡的一种方式 AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见, ...
- robotframework API 源码阅读笔记----robot.utils.asserts
http://robot-framework.readthedocs.io/en/latest/autodoc/robot.utils.html#robot.utils.asserts.assert_ ...
- 2018-2-13-WPF-延迟加载
title author date CreateTime categories WPF 延迟加载 lindexi 2018-2-13 17:23:3 +0800 2018-2-13 17:23:3 + ...
- 微信小程序(5)--阅读器
最近用微信小程序写了一个图书阅读器,可以实现左右滑动翻页,按钮翻页,上下滚动,切换背景,控制字体大小.以及记住设置好的状态,如页面再次进来保留上次的背景色和字体大小. 由于暂时没有真实的数据接口,所以 ...
- zabbix入门之定义触发器
zabbix入门之定义触发器 触发器的概念 触发器的定义:界定某特定的item 采集到数据的非合理区间或非合理状态.通常为逻辑表达式. 逻辑表达式(阈值):通常用于定义数据的不合理区间,其结果如下: ...
- java方法执行流程解析
Java程序运行时,必须经过编译和运行两个步骤.首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件.然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加 ...
- better-scroll 的使用
1.安装 cnpm install better-scroll --save 2.引入 import BScroll from "better-scroll"; 3.初始化 dat ...
- 如何将 不确定的有穷自动机(NFA) 转化为 确定的有穷自动机(DFA) 并将DFA最简化
一.从NFA到DFA的转换 例如下图: DFA的每个状态都是一个由NFA中的状态构成的集合,即NFA状态集合的一个子集 r =aa*bb*cc* 二.从带有ε-边的NFA到DFA的转换 r=0*1*2 ...
- Ubuntu 16.04配置vncviewer
网上有各种各样的教程,既混乱又复杂.这是提供一个亲自测试可用的配置方案,十分简单,桌面环境选用xfce,Ubuntu版本是16.04. 1 安装 Xfce 和 TightVNC sudo apt in ...
- 内存中的Buffer和Cache的区别
Reference:https://time.geekbang.org/column/article/74633 磁盘是一个块设备,可以划分为不同的分区:在分区之上再创建文件系统,挂载到某个目录,之后 ...