1

当通过invokevirtual 调用方法时,调用方法会弹出要与objectref 一起传递给被调用方法的值,并将它们放入新的堆栈帧中。

它如何知道哪个堆栈条目是 objectref?我的猜测是,它是通过查看被调用方法的类型并对其进行解析以确定要弹出多少值来实现的,但这似乎效率极低。还有其他一些我忽略的机制吗?

4

2 回答 2

1

当您使用类文件格式作为起点时,方法描述符是唯一确定操作数堆栈中的哪些值必须成为新堆栈帧的第一个局部变量的方法。

作为规则的一个例外,该invokeinterface指令具有一个嵌入式计数,可用于确定要消耗的(类型 1)元素的数量。正如文档所述:

调用接口指令的计数操作数记录参数值数量的度量,其中long类型或double类型的参数值对数值贡献两个单位,任何其他类型的参数贡献一个单位。该信息也可以从所选方法的描述符中导出。冗余是历史性的。

这种历史冗余并没有改变 JVM 必须处理方法描述符作为此信息源的事实,例如 for invokevirtualinvokestaticinvokespecialinvokedynamic。此外,需要符合要求的 JVM验证此信息invokeinterface,如果的计数与从方法描述符派生的计数不同,则抛出错误。

通常,验证者负责检测方法调用何时与堆栈帧的状态不一致,因此必须处理方法描述符并模拟它们对操作数堆栈的影响。这意味着,除非您使用的 JVM 在实际执行之前验证每条指令,否则即使不执行实际调用,它也必须处理这些描述符。显而易见的解决方案是在第一步中将方法描述符转换为更易于处理的内部表示。

简而言之,这些方法描述符效率低下,但是通过合理的 JVM 实现,您只需支付一次成本,而不是每次调用。

于 2021-11-22T13:26:55.350 回答
0

没有一种“正确”的方法可以做到这一点,但最简单的策略是将值留在堆栈上,并且被调用的方法通过负偏移量引用它们。例如,如果被调用的方法有 3 个参数,它们是从基堆栈偏移量减去 3、2 和 1 引用的。每个参数都被复制到一个局部变量,然后以通常的方式引用。可以更新堆栈偏移量以反映参数已被消耗。当然,每个本地参数也可以由一堆 pop 初始分配,每个参数一个。

可以执行其他技巧来加快速度。没有理由需要以不同于堆栈的方式存储局部变量。它们可以存储在堆栈本身上。传入的参数占用了它们在堆栈上的原始位置,然后通过更新堆栈偏移量为剩余的局部变量分配额外的空间。记住一个基本堆栈偏移量,并且所有局部变量都通过基本偏移量引用。

本质上,局部变量就像一个堆栈槽,除了它可以随时访问,而不管当前被压入顶部的是什么。

于 2021-11-20T06:29:07.737 回答