JVM如何决定JIT编译方法(将方法归类为“热门”)?

我已经使用-XX:+PrintCompilation ,我知道JIT编译器的基本技术以及使用JIT编译的原因。

然而,我仍然没有发现JVM如何决定JIT编译方法,即“当JIT编译方法的时候到了”。

我是否正确地假设每个方法都开始被解释,并且只要它不被归类为“热方法”它就不会被编译? 我有一些东西在脑后,我读到一个方法被认为是“热”,当它被执行至少10.000次(解释方法10.000次后,它将被编译),但我不得不承认我是不确定这个或我在哪里读到这个。

总结一下我的问题:

(1)只要没有将每种方法归类为“热”方法(并因此已被编译),或者是否有理由使用方法进行编译,即使它们不是“热”的,也会解释每种方法吗?

(2)JVM如何将方法分为“非热”和“热”方法? 执行次数? 还要别的吗?

(3)如果“热”方法存在某些阈值(如执行次数),是否有Java标志( -XX:... )来设置此阈值?

HotSpot编译策略相当复杂,特别是对于分层编译,它在Java 8中默认启用。它既不是多个执行,也不是CompileThreshold参数。

可以在HotSpot源中找到最佳解释(显然, 唯一合理的解释),请参阅advancedThresholdPolicy.hpp 。

我将总结这个高级编译策略的要点:

  • 执行从第0层(解释器)开始。
  • 编译的主要触发器是
    1. 方法调用计数器i ;
    2. 支持柜台b 。 后向分支通常表示代码中的循环。
  • 每次计数器达到特定频率值( TierXInvokeNotifyFreqLogTierXBackedgeNotifyFreqLog )时,都会调用编译策略来决定当前正在运行的方法下一步该做什么。 根据ib的值和C1和C2编译器线程的当前负载,可以决定

    • 继续执行翻译;
    • 开始在翻译中剖析;
    • 在第3层使用C1编译方法,并进行进一步重新编译所需的完整配置文件数据;
    • 在第2层使用C1编译方法,没有配置文件,但有可能重新编译(不太可能);
    • 最后在第1层使用C1编译方法,没有配置文件或计数器(也不太可能)。

    这里的关键参数是TierXInvocationThresholdTierXBackEdgeThreshold 。 可以根据编译队列的长度动态调整给定方法的阈值。

  • 编译队列不是FIFO,而是优先级队列。

  • 具有配置文件数据(第3层)的C1编译代码的行为类似,只是切换到下一级别(C2,第4层)的阈值要大得多。 例如,在大约200次调用之后,可以在第3层编译解释的方法,而在5000次调用之后,C1编译的方法可以在第4层进行重新编译。

  • 特殊策略用于方法内联。 即使它们不“热”,也可以将微小的方法内联到调用者中。 只有在频繁调用它们时才能内联更大的方法( InlineFrequencyRatioInlineFrequencyCount )。

控制它的主要参数是-XX:CompileThreshold=10000

Java 8的Hotspot现在默认使用分层编译,使用从1级到4级的多个编译阶段。我相信1不是优化。 级别3是C1(基于客户端客户端),级别4是C2(基于服务器编译器)

这意味着稍微优化可能比您预期的更早发生,并且在达到10K阈值后可以保持很长时间的优化。 我见过的最高点是在一百万次调用后消除StringBuilder的转义分析。

注意:循环迭代多次可以触发编译器。 例如,10K次的循环就足够了。

1)在方法被认为足够热之前,它被解释。 但是,一些JVM(例如Azul Zing)可以在启动时编译方法,您可以强制Hotspot JVM通过内部API编译方法。 Java 9也可能有一个AOT(Ahead Of Time)编译器,但它仍在研究AFAIK

2)呼叫次数或迭代次数。

3)是-XX:CompileThreshold=是主要的。