JVM如何决定对方法进行JIT编译(将方法分类为“热点”)?
JVM如何决定对方法进行JIT编译(将方法分类为“热点”)?
我已经使用过-XX:+PrintCompilation
,并且我了解JIT编译器的基本技术以及为什么要使用JIT编译。
然而,我仍然没有弄清楚JVM是如何决定何时对方法进行JIT编译的,即“何时是时候对方法进行JIT编译”。
我的假设是,每个方法开始时都会被解释执行,只有当它被归类为“热方法”时才会被编译。我记得曾经读到过,一个方法被执行至少10,000次后被认为是“热方法”(解释执行10,000次后,它将被编译),但我不确定这一点或者我在哪里读到的。
所以总结我的问题:
(1)只要方法没有被归类为“热方法”(因此没有被编译),每个方法都会被解释执行吗?还是有其他原因导致方法被编译而不是“热方法”?
(2)JVM如何将方法归类为“非热方法”和“热方法”?根据执行次数吗?还有其他的因素吗?
(3)如果存在某些阈值(如执行次数)来判断“热方法”,是否有Java标志(-XX:...
)可以设置这些阈值?
JVM决定JIT编译一个方法(将方法归类为“热方法”)的原因以及解决方法
Java虚拟机(JVM)决定何时JIT(即时编译)一个方法并将其分类为“热方法”是通过一些参数来控制的。其中一个主要参数是-XX:CompileThreshold=10000
。该参数用于指定方法被调用的次数达到多少时,JVM将对其进行JIT编译。
从Java 8开始,Hotspot默认使用分层编译(tiered compilation)的方式,共有4个级别的编译,分别是1到4级。其中1级表示没有优化,3级是C1编译器(基于客户端)和4级是C2编译器(基于服务器)。这意味着早期的一些优化可能会在预期之前发生,并且在达到10,000次调用的阈值后仍会继续优化。我曾经看到过一种情况是在一百万次调用后,逃逸分析消除了一个StringBuilder。
以下是JVM决定何时JIT编译一个方法的几个因素:
1)直到方法被认为足够“热”之前,它将被解释执行。但是某些JVM(例如Azul Zing)可以在启动时编译方法,并且您可以通过内部API强制Hotspot JVM编译方法。Java 9可能也有一个AOT(提前编译)编译器,但目前仍在研究中。
2)调用次数或迭代次数。
3)-XX:CompileThreshold=
是主要的控制参数。
在JVM更新的不同版本中,JIT编译方法的规则可能会有所变化。如果您多次调用相同的代码(但每次只调用一次),则可能会发现它们不会一次全部被编译。实际的编译算法更为复杂。获取更多信息最好的地方可能是查看JVM的源代码。其他任何已经编写的内容可能已经过时。
JIT编译是在后台进行的,如果有很多方法需要编译,这可能需要相当长的时间。当一个方法成为编译候选时,我们可以将其称为“热方法”。但是确切地说,方法何时被编译并替换现有代码是随机的且难以控制的。
希望以上信息对您有所帮助!如果有其他答案发布,我会继续关注,否则我会接受您的回答。
JVM如何决定对一个方法进行即时编译(将一个方法分类为“热点”)?
JVM的即时编译策略相当复杂,特别是在Java 8中默认启用的分层编译。它既不是执行次数的问题,也不是CompileThreshold
参数的问题。
对于这种高级编译策略,最好的解释(显然是唯一合理的解释)可以在HotSpot的源代码中找到,参见advancedThresholdPolicy.hpp。
我将总结这个高级编译策略的主要要点:
- 执行从第0层(解释器)开始。
- 主要的触发编译的条件是:
- 方法调用计数器i
;
- 回边计数器b
。回边通常表示代码中的循环。
- 每当计数器达到一定的频率值(TierXInvokeNotifyFreqLog
,TierXBackedgeNotifyFreqLog
)时,会调用编译策略来决定如何处理当前运行的方法。根据i
,b
的值以及当前C1和C2编译器线程的负载情况,可以决定:
- 在解释器中继续执行;
- 在解释器中开始进行分析;
- 使用C1在第3层对方法进行编译,需要完整的分析数据以便进行进一步的重新编译;
- 使用C1在第2层对方法进行编译,没有分析数据,但可以重新编译(不太可能);
- 最后使用C1在第1层对方法进行编译,没有分析数据或计数器(也不太可能)。
- 这里的关键参数是TierXInvocationThreshold
和TierXBackEdgeThreshold
。根据编译队列的长度,可以动态调整给定方法的阈值。
- 编译队列不是FIFO,而是优先队列。
- 使用包含完整信息的特殊策略进行方法内联。即使是非“热点”的小方法也可以被内联到调用者中。稍大一些的方法只有在经常被调用时才能被内联(InlineFrequencyRatio
,InlineFrequencyCount
)。
这个链接真的很有帮助,里面包含了我想知道的一切。你是对的,很难在外面找到合理的信息,我还没有阅读过关于分层编译的那么详细的东西,很可能以后也不会了。谢谢!
对我来说,关键的洞察力是正常路径是0(解释器)-> 3(C1,完整的分析)-> 4(C2)。在这条路径上,C1实际上只是用来收集供C2使用的分析数据。
然后有三条次要的替代路径。
(1) 如果C1编译发现方法是微不足道的,它会在1(C1,无分析)编译,因为4(C2)不会更快。
(2) 如果C2编译器很忙,方法会在2(C1,轻量级分析)编译,直到C2不那么忙为止,然后在3(C1,完整的分析)重新编译,以便可以继续到4(C2)。
(3) 如果C1很忙而C2不忙,分析将在解释器中进行,因此方法可以直接进入C2而不经过C1。
事实证明,关于10,000次调用的一般知识(包括我的知识)是错误的。谢谢你的完美答案。
如果有其他人正在寻找当前实现:链接,AdvancedThresholdPolicy已合并到SimpleThresholdPolicy中。
> 最后使用C1在第1层对方法进行编译,没有分析数据或计数器 < 这是否意味着第1层方法无法重新编译?当它们达到第4层的阈值时,它们是否会重新编译以确保获得C2的优化?感谢你详细解释这一切-几乎不可能找到相关信息!
在第1层编译方法意味着HotSpot认为该方法不会从更高层次的编译中受益。通常,这些是不需要复杂的C2优化的小简单方法。