jvm 内联最终方法吗?

梁雨生*_*梁雨生 2 java jvm hotspot

当我阅读 java8 规范时,我得到了这样的声明

在运行时,机器代码生成器或优化器可以“内联”最终方法的主体,用其主体中的代码替换方法的调用。

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.3.3

所以我的问题是热点真的内联了最终方法吗?

或者,是否只有最终方法可以内联?

apa*_*gin 8

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)


rzw*_*oot 5

它比那要复杂得多。

Hotspot 会做任何它需要做的事情来让事情运行得更快。唯一的规则是:它不能破坏 java 规范提供的任何保证。

这些保证没有提到诸如“这将被内联”之类的东西。这是不可能的观察,你是被内联,除非您尝试使用弄明白nanoTime(),和Java规范显然使得时序问题没有硬性保证-无法观察到的很少,如果保证在这样或那样的事情java规范 为什么要保证这样的事情?它只会妨碍 JVM 工程师。

当前流行的 JVM 实现使用这种粗略且过于简化的内联计划:

  1. 如果这样做是个好主意,任何方法,无论是否为final,都将被内联。

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

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

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

  5. 正如我所说,这就是它现在的工作方式。在未来的 Java 版本中它可能会以不同的方式工作,因为这些都不能保证。它也过于简单化了;例如,我没有提到像@jdk.internal.HotSpotIntrinsicCandidate或 之类的注释@java.lang.invokeForceInline。它不相关,您通常不应该编写带有关于内联如何工作的预设的代码。重点是你不需要知道。

  • 附录:即使非最终方法已被覆盖,JIT 也可能会在特定调用站点处内联该方法,前提是该特定调用站点仍然始终以原始方法结束,当然,需要进行快速预检查当假设不再成立时,就会去优化。因此,子类的加载并不一定会使所有内联代码失效,它可能只是引入了插入更多检查的必要性。通常,子类仅在某些地方使用,而大多数已运行的代码不会改变其行为。 (2认同)