Java要求对所有加载的类进行验证,以维护沙箱的安全性并确保代码可以安全地优化。注意,这是在字节码级别上完成的,所以验证并 不会
验证了Java的不变量 的语言 ,它只是验证字节码,根据字节码的规则是有道理的。
除其他外,字节码验证可确保指令格式正确,所有跳转均指向方法中的有效指令,以及所有指令均以正确类型的值进行 *** 作。最后一个是堆栈映射进入的位置。
问题是字节码本身不包含任何显式类型信息。类型是通过数据流分析隐式确定的。例如,iconstest指令创建一个整数值。如果将其存储在插槽1中,则该插槽现在具有一个int。如果控制流从存储浮点数的代码合并,则该插槽现在被认为具有无效的类型,这意味着在覆盖该值之前,您无法再对该值进行任何 *** 作。
从历史上看,字节码验证程序使用这些数据流规则来推断所有类型。不幸的是,不可能通过字节码在一次线性传递中推断出所有类型,因为向后跳转可能会使已经推断出的类型无效。经典的验证程序通过遍历代码直到一切都停止更改来解决此问题,这可能需要多次通过。
但是,验证会使Java中的类加载变慢。Oracle决定通过添加一个新的,更快的验证程序来解决此问题,该验证程序可以一次性验证字节码。为此,他们
需要从Java 7 (Java 6处于过渡状态) 开始的所有新类,
以携带有关其类型的元数据,以便可以通过一次验证字节码。由于字节码格式本身无法更改,因此此类型信息分别存储在名为的属性中
StackMapTable。
简单地在代码中的每个点存储每个值的类型显然会占用很多空间,并且非常浪费。为了使元数据更小,更有效,他们决定 只列出作为跳转目标的位置处的类型
。如果您考虑一下,这是您唯一一次需要额外信息来进行单次通过验证的情况。在跳转目标之间,所有控制流都是线性的,因此您可以使用旧的推理规则来在两个位置之间的类型中进行推理。
显式列出类型的每个位置称为堆栈映射框架。该
StackMapTable属性按顺序包含帧列表,尽管通常将它们表示为与前一帧的差异,以减小数据大小。如果该方法中没有框架(当控制流从不加入时会发生(即CFG是树)),则可以完全省略StackMapTable属性。
因此,这是StackMapTable如何工作以及为什么添加它的基本思想。最后一个问题是如何创建隐式初始帧。答案当然是,在方法开始时, *** 作数堆栈为空,并且局部变量槽具有由方法参数类型给定的类型,这些方法参数由方法分拆器确定。
如果您习惯使用Java,则方法参数类型在字节码级别上的工作方式会有一些细微的差异。首先,虚拟方法将隐式
this作为第一个参数。二是
boolean,
byte,
char,并
short不会在字节码级别存在。取而代之的是,它们都被实现为幕后的整数。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)