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 层开始(解释器)。
- 编译的主要触发器是
- 方法调用计数器
i
;
- 后台计数器
b
。向后分支通常表示代码中的循环。
每次计数器达到一定的频率值(TierXInvokeNotifyFreqLog
,TierXBackedgeNotifyFreqLog
),调用编译策略来决定当前运行方法下一步做什么.根据 i
、b
的值以及 C1 和 C2 编译器线程的当前负载,可以决定
- 在解释器中继续执行;
- 开始在解释器中进行分析;
- 使用第 3 层的 C1 编译方法,需要进一步重新编译所需的完整配置文件数据;
- 使用第 2 层 C1 的编译方法,没有配置文件但有可能重新编译(不太可能);
- 最终在第 1 层使用 C1 编译方法,没有配置文件或计数器(也不太可能)。
此处的关键参数是 TierXInvocationThreshold
和 TierXBackEdgeThreshold
。可以根据编译队列的长度为给定方法动态调整阈值。
编译队列不是FIFO,而是优先队列
具有配置文件数据(第 3 层)的 C1 编译代码的行为类似,只是切换到下一个级别(C2,第 4 层)的阈值要大得多。例如。解释方法可以在大约 200 次调用后在第 3 层编译,而 C1 编译的方法在 5000 次以上调用后需要在第 4 层重新编译。
- 方法内联使用特殊策略。微型方法可以内联到调用者中,即使它们不是 "hot"。稍微大一点的方法只有在被频繁调用时才可以内联(
InlineFrequencyRatio
, InlineFrequencyCount
).
我已经使用过 -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 层开始(解释器)。
- 编译的主要触发器是
- 方法调用计数器
i
; - 后台计数器
b
。向后分支通常表示代码中的循环。
- 方法调用计数器
每次计数器达到一定的频率值(
TierXInvokeNotifyFreqLog
,TierXBackedgeNotifyFreqLog
),调用编译策略来决定当前运行方法下一步做什么.根据i
、b
的值以及 C1 和 C2 编译器线程的当前负载,可以决定- 在解释器中继续执行;
- 开始在解释器中进行分析;
- 使用第 3 层的 C1 编译方法,需要进一步重新编译所需的完整配置文件数据;
- 使用第 2 层 C1 的编译方法,没有配置文件但有可能重新编译(不太可能);
- 最终在第 1 层使用 C1 编译方法,没有配置文件或计数器(也不太可能)。
此处的关键参数是
TierXInvocationThreshold
和TierXBackEdgeThreshold
。可以根据编译队列的长度为给定方法动态调整阈值。编译队列不是FIFO,而是优先队列
具有配置文件数据(第 3 层)的 C1 编译代码的行为类似,只是切换到下一个级别(C2,第 4 层)的阈值要大得多。例如。解释方法可以在大约 200 次调用后在第 3 层编译,而 C1 编译的方法在 5000 次以上调用后需要在第 4 层重新编译。
- 方法内联使用特殊策略。微型方法可以内联到调用者中,即使它们不是 "hot"。稍微大一点的方法只有在被频繁调用时才可以内联(
InlineFrequencyRatio
,InlineFrequencyCount
).