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 实现使用这个粗略和过于简化的内联计划:

  1. 任何方法,final 与否,如果看起来是个好主意,都将被内联。

  2. 这永远不会立即发生。只有选择方法 X 进行热点重新编译,并且 X 调用方法 Y,并且 Y 似乎是内联的好方法时才会发生。

  3. 如果 non-final 方法是内联的,这似乎是一个非法的举动。然而,事实并非如此:VM 只是做出了一个可证伪的假设:“此方法虽然不是最终方法,但不会被 VM 中加载的任何 class 中任何地方的任何内容覆盖”。如果现在为真,则可以内联该方法,即使以后可能不为真。

  4. 任何时候加载新的 classes(这归结为:一个新的 class 被扔到 ClassLoader 的本地 defineClass 方法),如果这导致任何假设不再为真,则进行检查。如果发生这种情况,依赖于此假设的所有方法的热点版本将 无效 并将恢复正常(缓慢,或多或少解释)操作。如果它们仍然 运行 很多,它们将再次成为热点,这一次请记住,由于该方法实际上已被覆盖,因此无法再轻松地对其进行内联。

  5. 正如我所说,这就是现在的工作方式。它在未来的 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