JVM 如何决定对方法进行 JIT 编译(将方法分类为 "hot")?

How does the JVM decided to JIT-compile a method (categorize a method as "hot")?

我已经使用过 -XX:+PrintCompilation,我知道 JIT 编译器的基本技术以及为什么要使用 JIT 编译。

但我仍然没有发现 JVM 是如何决定 JIT 编译方法的,即 "when the right time has come to JIT-compile a method"。

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

总结一下我的问题:

(1) every 方法是否被解释为只要它没有被归类为 "hot" 方法(因此已被编译)或者是否有方法的原因即使它们不是 "hot" 也要编译?

(2) JVM如何将方法分为"non-hot"和"hot"两种方法?执行次数?还有什么吗?

(3) 如果 "hot" 方法有一定的阈值(比如执行次数),是否有 Java 标志(-XX:...)来设置这个阈值?

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

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

这意味着可以比您预期的更早进行一些优化,并且可以在达到 10K 阈值后很长时间内继续优化。我见过的最高的是逃逸分析在一百万次调用后消除了一个 StringBuilder。

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

1) 直到一个方法被认为足够热,它才会被解释。然而,某些 JVM(例如 Azul Zing)可以在启动时编译方法,您可以强制 Hotspot JVM 通过内部 API 编译方法。 Java 9 可能也有一个 AOT(提前)编译器,但它仍在研究中 AFAIK

2) 调用次数,或迭代次数。

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

HotSpot编译策略比较复杂,尤其是Tiered Compilation,在Java8中默认开启,既不是执行次数,也不是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 层编译,而 C1 编译的方法在 5000 次以上调用后需要在第 4 层重新编译。

  • 方法内联使用特殊策略。微型方法可以内联到调用者中,即使它们不是 "hot"。稍微大一点的方法只有在被频繁调用时才可以内联(InlineFrequencyRatio, InlineFrequencyCount).