JVM 如何在加载类时验证没有潜在的操作数堆栈溢出?

Posted

技术标签:

【中文标题】JVM 如何在加载类时验证没有潜在的操作数堆栈溢出?【英文标题】:How can the JVM verify there's no potential operand stack overflow when loading a class? 【发布时间】:2012-05-19 11:19:09 【问题描述】:

回顾一些演示,我发现了以下说法:当 JVM 加载一个类时,它可以分析其内容并确保 操作数堆栈没有溢出或下溢。我发现 a lot of sources 提出了同样的主张,但没有具体说明它是如何完成的。

我不清楚如何使用静态分析进行此类验证。假设我有一个(恶意)方法,它获取一些值作为参数,并使用它来执行一系列弹出。在加载时,迭代次数是未知的,因为它取决于方法调用者给出的参数。因此,在我看来,只有在运行时才能确定是否会出现下溢。我在这里错过了什么?

【问题讨论】:

验证器可能会拒绝任何在循环中弹出的尝试。 @MarkoTopolnik,我用pop 作为最清楚的例子。也可以使用其他弹出命令,例如各种 stores。 好的,那么您是否看到任何合法的情况,即相对于循环步骤中的推送,代码会有过多的弹出(通过任何指令)? @MarkoTopolnik,绝对不是,我认为这不能通过编译 Java 来实现。但是验证是为了防止出现错误、恶意或损坏的类。据我了解,设计者确实假设可能会加载攻击性类,因此他们添加了验证。 我的意思是指出一种模式,它对验证器来说是可行的,可以检测并涵盖您在问题中提到的所有情况,同时不会导致任何误报。 【参考方案1】:

您可以在Java Virtual Machine specification 中找到字节码验证器的基本描述。

简单来说,每个分支点的堆栈深度都是已知的,在同一个合并点合并的两条执行路径也必须具有相同的堆栈深度。因此,验证器不允许您执行一系列没有相应 put 的 pop。

【讨论】:

感谢您的链接(以及简单的解释)。我想知道这种限制对运行时字节码效率的影响是什么(例如,不能有一个推送东西的循环,然后是一个在弹出它时使用这些东西的循环)。但这超出了这个问题的范围。 我知道的唯一问题是尾递归优化。但它可以在方法级别解决,因此适用相同的验证规则。如果我没记错的话,JVM 中的工作已经完成以支持它。它不会影响字节码,但会影响将其转换为本机代码的 JIT 编译器。 抛出异常时执行跳转到catch块时如何解决?当捕获到异常时,操作数堆栈将被清除,异常的实例将被推送到它。【参考方案2】:

方法的代码被分割成执行块。 “块”是无需跳出或跳入即可执行的指令序列。这些块构建了所有可能执行路径的有向图。

一个块总是期望在它的开头有一个特定的堆栈大小,并且在它的末尾有一个固定的堆栈大小(开始 + 所有推送 - 所有弹出)。验证器检查从给定块“b”可以到达的所有块“a”,b 的结束堆栈大小与 a 的开始堆栈大小匹配。

【讨论】:

以上是关于JVM 如何在加载类时验证没有潜在的操作数堆栈溢出?的主要内容,如果未能解决你的问题,请参考以下文章

jvm MetaSpace内存溢出

JVM中的类加载机制

内存溢出与jvm参数配置

JVM面试题大全

大厂必问的JVM面试题

JVM类加载原理学习笔记