什么是堆栈映射框架

时间:2014-08-03 23:08:10

标签: java jvm bytecode stackframe

我最近一直在关注Java Virtual Machine Specifications(JVMS),试图更好地了解我的程序是如何工作的,但我找到了一个我不太喜欢的部分......

4.7.4节描述了 StackMapTable 属性,在该部分中,文档详细介绍了堆栈映射帧。问题在于它有点罗嗦,我通过榜样学得最好;不是通过阅读。

据我所知,第一个堆栈映射帧是从方法描述符派生的,但是我不明白怎么回事(据说这是here解释的。)另外,我还不完全明白堆栈映射帧是什么做。我认为它们与Java中的块类似,但看起来好像你不能在彼此内部堆栈映射帧。

无论如何,我有两个具体问题:

  • 堆栈映射框架有什么作用?
  • 如何创建第一个堆栈映射框?

和一个一般问题:

  • 有人能提供的解释比JVMS中给出的解释更少,更容易理解吗?

1 个答案:

答案 0 :(得分:116)

Java要求验证所有加载的类,以便维护沙箱的安全性并确保代码可以安全地进行优化。请注意,这是在字节码级别完成的,因此验证验证Java 语言的不变量,它只是根据字节码规则验证字节码是否有意义

除其他外,字节码验证可确保指令格式正确,所有跳转都是方法中的有效指令,并且所有指令都对正确类型的值进行操作。最后一个是堆栈映射的来源。

事情是字节码本身不包含显式类型信息。通过数据流分析隐式确定类型。例如,iconst指令创建一个整数值。如果将其存储在插槽1中,则该插槽现在具有int。如果控制流从存储浮点数的代码合并而来,则插槽现在被认为具有无效类型,这意味着您无法对该值执行任何操作,直到覆盖它为止。

历史上,字节码验证器使用这些数据流规则推断出所有类型。不幸的是,不可能通过字节码推断单个线性传递中的所有类型,因为向后跳转可能使已经推断的类型无效。经典验证程序通过迭代代码直到一切都停止变化来解决这个问题,可能需要多次传递。

但是,验证会使Java中的类加载速度变慢。 Oracle决定通过添加一个新的,更快的验证器来解决这个问题,它可以在一次通过中验证字节码。为此,他们要求所有以Java 7开头的新类(Java 6处于过渡状态)携带有关其类型的元数据,以便可以一次性验证字节码。由于字节码格式本身无法更改,因此此类型信息会单独存储在名为StackMapTable的属性中。

只需在代码中的每个单点存储每个值的类型,显然会占用大量空间并且非常浪费。为了使元数据更小更高效,他们决定让仅列出跳转目标位置的类型。如果您考虑一下,这是您需要额外信息才能进行单程验证的唯一时间。在跳转目标之间,所有控制流都是线性的,因此您可以使用旧的推理规则推断位置之间的类型。

显式列出类型的每个位置称为堆栈映射框架。 StackMapTable属性按顺序包含帧列表,但它们通常表示为与前一帧的差异,以减少数据大小。如果方法中没有框架,当控制流从不加入时发生(即CFG是树),则可以完全省略StackMapTable属性。

因此,这是StackMapTable如何工作以及添加原因的基本思路。最后一个问题是如何创建隐式初始帧。答案当然是在方法的开头,操作数堆栈为空,局部变量槽具有方法参数类型给出的类型,这些类型是从方法描述符确定的。

如果您习惯使用Java,那么方法参数类型在字节码级别的工作方式会有一些细微差别。首先,虚方法有一个隐式this作为第一个参数。其次,booleanbytecharshort在字节码级别不存在。相反,它们都是在幕后实现的。