jvm 是否内联最终方法?
Does jvm inline the final method?
当我阅读 java8 规范时,我得到的声明是
At run time, a machine-code generator or optimizer can "inline" the body of a final method, replacing an invocation of the method with the code in its body.
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.3.3
所以我的问题是热点是否真的内联最终方法?
或者,是否只有final方法可以内联?
比那要复杂得多。
Hotspot 会尽一切努力使 运行 变得更快。唯一的规则是:它不能破坏 java 规范提供的任何保证。
这些保证没有提到 'this will be inlined' 之类的事情。 不可能 观察到您正在被内联,除非您尝试使用 nanoTime()
来弄清楚它,并且 java 规范显然没有做出硬性保证时间问题 - 无法观察到的事情很少在 java 规范中以某种方式得到保证。为什么要费心去保证这些事情呢?它只会妨碍 JVM 工程师。
当前流行的 JVM 实现使用这个粗略和过于简化的内联计划:
任何方法,final 与否,如果看起来是个好主意,都将被内联。
这永远不会立即发生。只有选择方法 X 进行热点重新编译,并且 X 调用方法 Y,并且 Y 似乎是内联的好方法时才会发生。
如果 non-final 方法是内联的,这似乎是一个非法的举动。然而,事实并非如此:VM 只是做出了一个可证伪的假设:“此方法虽然不是最终方法,但不会被 VM 中加载的任何 class 中任何地方的任何内容覆盖”。如果现在为真,则可以内联该方法,即使以后可能不为真。
任何时候加载新的 classes(这归结为:一个新的 class 被扔到 ClassLoader
的本地 defineClass
方法),如果这导致任何假设不再为真,则进行检查。如果发生这种情况,依赖于此假设的所有方法的热点版本将 无效 并将恢复正常(缓慢,或多或少解释)操作。如果它们仍然 运行 很多,它们将再次成为热点,这一次请记住,由于该方法实际上已被覆盖,因此无法再轻松地对其进行内联。
正如我所说,这就是现在的工作方式。它在未来的 java 版本中可能会有所不同,因为 none 是有保证的。它也过于简单化了;例如,我没有提到像 @jdk.internal.HotSpotIntrinsicCandidate
或 @java.lang.invokeForceInline
这样的注释。这无关紧要,通常您不应该在编写代码时先假设内联是如何工作的。关键是你不需要知道。
HotSpot 内联策略远非微不足道。有很多影响内联。
最重要的是方法的大小和“热度”,以及总的内联深度。一个方法是否是最终的,不重要。
HotSpot 也可以轻松地内联虚拟方法。如果频繁接收者不超过 2 个,它甚至可以内联 polymorphic 方法。有一个 epic post 详细描述了这种多态调用是如何工作的。
要分析方法在特定情况下的内联方式,请使用以下诊断 JVM 选项:
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
这将输出完整的编译树,其中包含每个方法的原因,内联或未内联的原因:
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
当我阅读 java8 规范时,我得到的声明是
At run time, a machine-code generator or optimizer can "inline" the body of a final method, replacing an invocation of the method with the code in its body.
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.3.3
所以我的问题是热点是否真的内联最终方法?
或者,是否只有final方法可以内联?
比那要复杂得多。
Hotspot 会尽一切努力使 运行 变得更快。唯一的规则是:它不能破坏 java 规范提供的任何保证。
这些保证没有提到 'this will be inlined' 之类的事情。 不可能 观察到您正在被内联,除非您尝试使用 nanoTime()
来弄清楚它,并且 java 规范显然没有做出硬性保证时间问题 - 无法观察到的事情很少在 java 规范中以某种方式得到保证。为什么要费心去保证这些事情呢?它只会妨碍 JVM 工程师。
当前流行的 JVM 实现使用这个粗略和过于简化的内联计划:
任何方法,final 与否,如果看起来是个好主意,都将被内联。
这永远不会立即发生。只有选择方法 X 进行热点重新编译,并且 X 调用方法 Y,并且 Y 似乎是内联的好方法时才会发生。
如果 non-final 方法是内联的,这似乎是一个非法的举动。然而,事实并非如此:VM 只是做出了一个可证伪的假设:“此方法虽然不是最终方法,但不会被 VM 中加载的任何 class 中任何地方的任何内容覆盖”。如果现在为真,则可以内联该方法,即使以后可能不为真。
任何时候加载新的 classes(这归结为:一个新的 class 被扔到
ClassLoader
的本地defineClass
方法),如果这导致任何假设不再为真,则进行检查。如果发生这种情况,依赖于此假设的所有方法的热点版本将 无效 并将恢复正常(缓慢,或多或少解释)操作。如果它们仍然 运行 很多,它们将再次成为热点,这一次请记住,由于该方法实际上已被覆盖,因此无法再轻松地对其进行内联。正如我所说,这就是现在的工作方式。它在未来的 java 版本中可能会有所不同,因为 none 是有保证的。它也过于简单化了;例如,我没有提到像
@jdk.internal.HotSpotIntrinsicCandidate
或@java.lang.invokeForceInline
这样的注释。这无关紧要,通常您不应该在编写代码时先假设内联是如何工作的。关键是你不需要知道。
HotSpot 内联策略远非微不足道。有很多
最重要的是方法的大小和“热度”,以及总的内联深度。一个方法是否是最终的,不重要。
HotSpot 也可以轻松地内联虚拟方法。如果频繁接收者不超过 2 个,它甚至可以内联 polymorphic 方法。有一个 epic post 详细描述了这种多态调用是如何工作的。
要分析方法在特定情况下的内联方式,请使用以下诊断 JVM 选项:
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
这将输出完整的编译树,其中包含每个方法的原因,内联或未内联的原因:
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