什么是堆栈映射框架

Ste*_*ven 55 java jvm bytecode stack-frame

我最近一直在研究Java虚拟机规范(JVMS),试图更好地理解我的程序是如何工作的,但我找到了一个我不太了解的部分......

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

我知道第一个堆栈映射帧是从方法描述符派生的,但我不明白如何(这应该在这里解释.)另外,我不完全理解堆栈映射帧的作用.我认为它们与Java中的块类似,但看起来好像你不能在彼此内部堆栈映射帧.

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

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

还有一个普遍的问题:

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

Ant*_*ony 124

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

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

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

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

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

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

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

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

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

  • 一个不起眼的话题的伟大解释. (11认同)
  • 作为对最后一段的修改,`long`和`double`参数将像所有局部变量一样,在堆栈帧中消耗*两个*局部变量. (9认同)