对于需要动态绑定的虚方法调用来说,即时编译器则需要先对虚方法调用进行去虚化(devirtualize),即转换为一个或多个直接调用,然后才能进行方法内联。

    即时编译器的去虚化方式可分为完全去虚化以及条件去虚化(guarded devirtualization)。

    完全去虚化
    通过类型推导或者类层次分析识别虚方法调用的唯一目标方法。关键在于虚方法调用的目标方法是唯一。
    基于类型推导的完全去虚化将通过数据流分析推导出调用者的动态类型,从而确定具体的目标方法。C2 和 Graal 都没有这么做:一是该优化属于全局优化,比较浪费时间;二是有后续类层次分析去虚化和条件去虚化兜底,能覆盖大部分的代码情况。
    基于类层次分析的完全去虚化通过分析 Java 虚拟机中所有已被加载的类,判断某个抽象方法或者接口方法是否仅有一个实现。如果是,那么对这些方法的调用将只能调用至该具体实现中。Java 虚拟机的做法是为当前编译结果注册若干个假设(assumption),每当新的类被加载,Java 虚拟机便会重新验证这些假设。如果某个假设不再成立,那么 Java 虚拟机便会对其所属的编译结果进行去优化。

    条件去虚化
    将虚方法调用转换为若干个类型测试以及直接调用的一种优化手段。它的关键在于找出需要进行比较的类型。
    将调用者的动态类型,依次与 Java 虚拟机所收集的类型 Profile 中记录的类型相比较。如果匹配,则直接调用该记录类型所对应的目标方法。
    第一,如果类型 Profile 是完整的,也就是说,所有出现过的动态类型都被记录至类型 Profile 之中,那么即时编译器可以让程序进行去优化,重新收集类型 Profile。
    如果匹配不到动态类型时,如果类型 Profile 是不完整的,某些出现过的动态类型并没有记录至类型 Profile 之中,那么重新收集并没有多大作用。即时编译器可以让程序进行原本的虚调用,通过内联缓存进行调用,或者通过方法表进行动态绑定。
    在 C2 中,如果类型 Profile 是不完整的,即时编译器压根不会进行条件去虚化,而是直接使用内联缓存或者方法表。