当我阅读 java8 规范时,我得到了这样的声明
在运行时,机器代码生成器或优化器可以“内联”最终方法的主体,用其主体中的代码替换方法的调用。
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.3.3
所以我的问题是热点真的内联了最终方法吗?
或者,是否只有最终方法可以内联?
HotSpot 内联策略绝非易事。影响内联的因素和启发法有很多。
最重要的是方法的大小和“热度”,以及总内联深度。一个方法是否是最终的,并不重要。
HotSpot 也可以轻松内联虚拟方法。如果频繁接收器不超过 2 个,它甚至可以内联多态方法。有一篇史诗般的帖子详细描述了这种多态调用是如何工作的。
要分析在特定情况下如何内联方法,请使用以下诊断 JVM 选项:
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
Run Code Online (Sandbox Code Playgroud)
这将输出带有每个方法的原因的完整编译树,为什么它被内联:
java.util.regex.Pattern$Start::match (90 bytes)
@ 44 java.util.regex.Pattern$BmpCharProperty::match (55 bytes) inline (hot)
\-> TypeProfile (331146/331146 counts) = java/util/regex/Pattern$BmpCharProperty
@ 14 java.lang.String::charAt (25 bytes) inline (hot)
\-> TypeProfile (502732/502732 counts) = java/lang/String
@ 1 java.lang.String::isLatin1 (19 bytes) inline (hot)
@ 12 java.lang.StringLatin1::charAt (28 bytes) inline (hot)
@ 21 java.lang.StringUTF16::charAt (11 bytes) inline (hot)
@ 2 java.lang.StringUTF16::checkIndex (9 bytes) inline (hot)
@ 2 java.lang.StringUTF16::length (5 bytes) inline (hot)
@ 5 java.lang.String::checkIndex (46 bytes) inline (hot)
@ 7 java.lang.StringUTF16::getChar (60 bytes) (intrinsic)
@ 19 java.util.regex.Pattern$CharPredicate::is (0 bytes) virtual call
@ 36 java.util.regex.Pattern$Branch::match (66 bytes) inline (hot)
@ 36 java.util.regex.Pattern$GroupTail::match (111 bytes) inline (hot)
\-> TypeProfile (56997/278159 counts) = java/util/regex/Pattern$GroupTail
\-> TypeProfile (221162/278159 counts) = java/util/regex/Pattern$Branch
@ 70 java.util.regex.Pattern$BranchConn::match (11 bytes) inline (hot)
@ 70 java.util.regex.Pattern$LastNode::match (45 bytes) inline (hot)
\-> TypeProfile (56854/113708 counts) = java/util/regex/Pattern$LastNode
\-> TypeProfile (56854/113708 counts) = java/util/regex/Pattern$BranchConn
@ 7 java.util.regex.Pattern$Branch::match (66 bytes) inline (hot)
\-> TypeProfile (56598/56598 counts) = java/util/regex/Pattern$Branch
@ 32 java.util.regex.Pattern$Branch::match (66 bytes) inline (hot)
@ 32 java.util.regex.Pattern$GroupHead::match (47 bytes) already compiled into a big method
\-> TypeProfile (66852/267408 counts) = java/util/regex/Pattern$GroupHead
\-> TypeProfile (200556/267408 counts) = java/util/regex/Pattern$Branch
@ 32 java.util.regex.Pattern$Branch::match (66 bytes) recursive inlining is too deep
@ 32 java.util.regex.Pattern$GroupHead::match (47 bytes) already compiled into a big method
\-> TypeProfile (66852/267408 counts) = java/util/regex/Pattern$GroupHead
\-> TypeProfile (200556/267408 counts) = java/util/regex/Pattern$Branch
@ 50 java.util.regex.Pattern$GroupHead::match (47 bytes) already compiled into a big method
\-> TypeProfile (334260/334260 counts) = java/util/regex/Pattern$GroupHead
Run Code Online (Sandbox Code Playgroud)
它比那要复杂得多。
Hotspot 会做任何它需要做的事情来让事情运行得更快。唯一的规则是:它不能破坏 java 规范提供的任何保证。
这些保证没有提到诸如“这将被内联”之类的东西。这是不可能的观察,你是被内联,除非您尝试使用弄明白nanoTime()
,和Java规范显然使得时序问题没有硬性保证-无法观察到的很少,如果保证在这样或那样的事情java规范 为什么要保证这样的事情?它只会妨碍 JVM 工程师。
当前流行的 JVM 实现使用这种粗略且过于简化的内联计划:
如果这样做是个好主意,任何方法,无论是否为final,都将被内联。
这永远不会立即发生。只有在选择方法 X 进行热点重新编译,并且 X 调用方法 Y 时才会发生这种情况,而 Y 似乎是内联的好方法。
如果内联了非最终方法,这似乎是非法的举动。然而,事实并非如此:VM 只是做了一个可证伪的假设:“这个方法虽然不是最终的,但不会被 VM 中加载的任何类中的任何地方覆盖”。如果现在为真,则可以内联该方法,即使以后可能不为真。
任何时候加载新类(这归结为:在ClassLoader
的本地defineClass
方法中抛出一个新类),如果这导致任何假设不再成立,就会进行检查。如果发生这种情况,所有依赖于该假设的方法的热点版本都将失效,并将恢复到正常(缓慢的,或多或少解释过的)操作。如果它们仍然运行很多,它们将再次成为热点,这一次请记住,由于该方法实际上已被覆盖,因此无法再轻松地内联。
正如我所说,这就是它现在的工作方式。在未来的 Java 版本中它可能会以不同的方式工作,因为这些都不能保证。它也过于简单化了;例如,我没有提到像@jdk.internal.HotSpotIntrinsicCandidate
或 之类的注释@java.lang.invokeForceInline
。它不相关,您通常不应该编写带有关于内联如何工作的预设的代码。重点是你不需要知道。
归档时间: |
|
查看次数: |
150 次 |
最近记录: |