关于jvm method inlining优化对栈帧的影响

subchen 2013-10-14
我们知道,jvm 支持 method inlining 优化。

但是我对于优化后的行为对栈帧的影响有一些不太理解的地方:

1.解析器会不会也做这样的优化?还是只是在 JIT 编译的时候使用?
2.如果方法被inline,那么如果在原本inline的方法内查看当前栈帧的情况,比如抛出Exception,或者用 jstack 查看 thread stack,那么是看到 inline 之后的情况,还是会被恢复成 inline 之前的情况?
3.如果栈帧是 inline 之后的情况,那么如果此时 jit 编译执行因为激进优化失败的情况,需要退回到解释模式下继续执行,那么这个时候的 java 栈帧又会变得如何了?

请帮忙解释一下,谢谢!
RednaxelaFX 2013-10-14
这些都是好问题。下面一一回答:

subchen 写道
1.解析器会不会也做这样的优化?还是只是在 JIT 编译的时候使用?

是说“解释器”吧(而不是“解析器”哦)。

理论上说解释器也完全可以做方法内联的优化。但三个主流桌面/服务器端JVM里,HotSpot VM、J9 VM这俩JVM都不在解释器里做这种优化,而JRockit VM则没有解释器。它们仨都只在动态编译的时候才做方法调用的内联。

纯解释器的话要如何做内联呢?解释器也有很多种,有读入虚拟指令就直接开始解释执行的,也有先对读入的虚拟指令做一些处理再解释执行的,也有上述两种方式的混合体。

先做处理再解释执行的有许多例子。一种是用direct-threading方式实现的解释器。作为实际案例,JamVM的解释器就可以选择使用direct-threading模式。它会先把输入的Java字节码转换为它的内部形式,以便加速后面的解释执行。

混合的方式,可以参考许多JVM的解释器都会采用的“quickening”技巧:某些带有符号参数的指令,第一次执行的之后把符号解析(resolve)成直接引用,并把原指令改写为不再检查是否已解析的版本。像getstatic、invokestatic、new之类的很多指令都是该技巧的应用场景。HotSpot、J9的解释器都有使用该技巧。

可以看到就算现有的常见的JVM的解释器也未必是直接执行输入的字节码,而可能会对字节码做“转换”或“改写”再解释执行。那么如果制作一个解释器,在预处理的步骤里先做方法内联,然后再解释执行,也是完全可行的。只是我没留意过有实际实现这么做而已。这涉及一个取舍:想追求速度的会上动态编译,而只用解释器的通常追求实现的简单直观;在预处理里做方法调用内联在这两种取舍方向上都显得尴尬。

subchen 写道
2.如果方法被inline,那么如果在原本inline的方法内查看当前栈帧的情况,比如抛出Exception,或者用 jstack 查看 thread stack,那么是看到 inline 之后的情况,还是会被恢复成 inline 之前的情况?


HotSpot VM的话,Throwable对象里抓取到的stack trace,与jstack看到的,都反映逻辑上的Java栈的情况,而不是内联过后的情况,这些在HotSpot VM里叫做virtual frame(或者VFrame),并非执行方法时实际的栈帧状况。实际的状况就是带有内联的方法的栈帧的内容融合了原本多个逻辑栈帧的。HotSpot VM在动态编译Java方法的时候,如果有内联,会在生成的代码附带的元数据里记录下内联了什么,以便在需要的时候还原出逻辑栈帧信息出来。

subchen 写道
3.如果栈帧是 inline 之后的情况,那么如果此时 jit 编译执行因为激进优化失败的情况,需要退回到解释模式下继续执行,那么这个时候的 java 栈帧又会变得如何了?

Deoptimize的时候,有内联的方法的栈帧会被unpack成多个解释器模式格式的栈帧,恢复出原本非内联、解释器能识别的状态,然后回到解释器里去执行。
涉及很多细节,有兴趣的话再展开具体讨论。
subchen 2013-10-14
呵呵,解释的非常清楚。

原来还有个虚拟栈帧(virtual frame)来处理 inline 优化带来的问题。
这样在Java语言层面就不会有不一致的情况发生了。

非常感谢!
Global site tag (gtag.js) - Google Analytics