JVM会内联最终方法吗?

huangapple go评论63阅读模式
英文:

Does jvm inline the final method?

问题

当我阅读Java 8规范时,我看到了这样的说明:
> 在运行时,机器码生成器或优化器可以将最终方法的主体进行“内联”,将对该方法的调用替换为其主体中的代码。

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

所以我的问题是,HotSpot JVM真的会内联最终方法吗?

或者,只有最终方法可以被内联吗?

英文:

When I read the java8 spec, I get the declaration that
> 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

So my question is that does the hotspot really inline the final method?

Or, is there only the final method can be inlined?

答案1

得分: 8

HotSpot的内联策略远非简单。有许多影响内联的因素和启发式方法。

最重要的是方法的大小和“热度”,以及总内联深度。方法是否是final的并不重要。

HotSpot甚至可以轻松内联虚方法。如果没有超过2个频繁的接收者,它甚至可以内联多态方法。有一篇详细描述这种多态调用工作原理的史诗级文章。

要分析特定情况下方法的内联情况,请使用以下诊断性JVM选项:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining

这将输出完整的编译树,其中包含每个方法的原因,为什么它被内联或未被内联:

java.util.regex.Pattern$Start::match (90字节)
   @ 44   java.util.regex.Pattern$BmpCharProperty::match (55字节)   内联 ()
    \-> TypeProfile (331146/331146 计数) = java/util/regex/Pattern$BmpCharProperty
     @ 14   java.lang.String::charAt (25字节)   内联 ()
      \-> TypeProfile (502732/502732 计数) = java/lang/String
       @ 1   java.lang.String::isLatin1 (19字节)   内联 ()
       @ 12   java.lang.StringLatin1::charAt (28字节)   内联 ()
       @ 21   java.lang.StringUTF16::charAt (11字节)   内联 ()
         @ 2   java.lang.StringUTF16::checkIndex (9字节)   内联 ()
           @ 2   java.lang.StringUTF16::length (5字节)   内联 ()
           @ 5   java.lang.String::checkIndex (46字节)   内联 ()
         @ 7   java.lang.StringUTF16::getChar (60字节)   (内部方法)
     @ 19   java.util.regex.Pattern$CharPredicate::is (0字节)   虚调用
     @ 36   java.util.regex.Pattern$Branch::match (66字节)   内联 ()
     @ 36   java.util.regex.Pattern$GroupTail::match (111字节)   内联 ()
      \-> TypeProfile (56997/278159 计数) = java/util/regex/Pattern$GroupTail
      \-> TypeProfile (221162/278159 计数) = java/util/regex/Pattern$Branch
       @ 70   java.util.regex.Pattern$BranchConn::match (11字节)   内联 ()
       @ 70   java.util.regex.Pattern$LastNode::match (45字节)   内联 ()
        \-> TypeProfile (56854/113708 计数) = java/util/regex/Pattern$LastNode
        \-> TypeProfile (56854/113708 计数) = java/util/regex/Pattern$BranchConn
         @ 7   java.util.regex.Pattern$Branch::match (66字节)   内联 ()
          \-> TypeProfile (56598/56598 计数) = java/util/regex/Pattern$Branch
           @ 32   java.util.regex.Pattern$Branch::match (66字节)   内联 ()
           @ 32   java.util.regex.Pattern$GroupHead::match (47字节)   已编译为大方法
            \-> TypeProfile (66852/267408 计数) = java/util/regex/Pattern$GroupHead
            \-> TypeProfile (200556/267408 计数) = java/util/regex/Pattern$Branch
             @ 32   java.util.regex.Pattern$Branch::match (66字节)   递归内联太深
             @ 32   java.util.regex.Pattern$GroupHead::match (47字节)   已编译为大方法
              \-> TypeProfile (66852/267408 计数) = java/util/regex/Pattern$GroupHead
              \-> TypeProfile (200556/267408 计数) = java/util/regex/Pattern$Branch
             @ 50   java.util.regex.Pattern$GroupHead::match (47字节)   已编译为大方法
              \-> TypeProfile (334260/334260 计数) = java/util/regex/Pattern$GroupHead

英文:

HotSpot inlining policy is far from being trivial. There are many factors and heuristics that affect inlining.

What matters most, is the size and the "hotness" of the method, and the total inlining depth. Whether a method is final or not, is not important.

HotSpot can easily inline virtual methods, too. It can even inline polymorphic methods, if there are no more than 2 frequent receivers. There is an epic post describing in detail how such polymorhic calls work.

To analyze how methods are inlined in a particular case, use the following diagnostic JVM options:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining

This will output the full compilation tree with a reason for each method, why it was inlined or not:

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

答案2

得分: 5

这比那复杂得多。

Hotspot将尽其所能以使事情运行迅速。唯一的规则是:它不能违反Java规范提供的任何保证。

这些保证并不包括诸如“这将被内联”的内容。除非你尝试使用nanoTime()来找出是否被内联,否则不可能观察到你正在被内联——而且Java规范对于时间问题显然没有硬性保证——在Java规范中,很少有事情在一方面或另一方面上得到保证,因为很少能观察到不可观察的事情。为什么要保证这样的事情呢?这只会使JVM工程师们受阻。

目前流行的JVM实现对内联使用了这个粗略且过于简化的计划:

  1. 任何方法,无论是否为final,如果看起来内联是个好主意,就会被内联。

  2. 这永远不会立即发生。只有在方法X被选择进行热重编译,并且X调用方法Y时,Y看起来才适合内联。

  3. 如果内联一个非final方法,这似乎是一种不合法的举动。然而,事实并非如此:虚拟机只是做出了一个可以被证伪的假设:“尽管这个方法不是final的,但它在VM中加载的任何类中都没有被覆盖”。如果这个假设现在是真的,那么这个方法就可以被内联,即使以后可能不再成立。

  4. 每当加载新的类时(这归结为:新的类被传递给ClassLoader的本机defineClass方法),都会检查是否有任何假设不再成立。如果发生这种情况,依赖于这个假设的所有方法的热点版本将被无效化,并将恢复到正常(较慢,更多或多少是解释的)操作。如果它们仍然经常运行,它们将再次被热点化,这次会记住由于实际上被覆盖而使方法很难再次被内联。

  5. 正如我所说,这只是目前的工作原理。未来的Java版本可能会有所不同,因为这一切都没有保证。这也是过于简化的;例如,我没有提到诸如@jdk.internal.HotSpotIntrinsicCandidate@java.lang.invokeForceInline之类的注解。这些都不相关,作为规则,你不应该根据内联的工作方式进行编码。整个重点是你不需要知道。

英文:

It's way more complicated than that.

Hotspot will do whatever it needs to do to make things run fast. The only rule is: It cannot break any of the guarantees provided by the java specification.

These guarantees do not mention things like 'this will be inlined'. It is not possible to observe that you are being inlined unless you try to figure it out by using nanoTime(), and the java spec obviously makes no hard guarantees on timing issues - things that cannot be observed are rarely if ever guaranteed one way or another in the java spec. Why bother guaranteeing such things? It only handicaps the JVM engineers.

Current popular JVM implementations use this rough and oversimplified plan for inlining:

  1. Any method, final or not, will be inlined if it seems like a good idea to do that.

  2. This never happens immediately. It only happens if method X is selected for hotspot recompilation, and X calls method Y, and Y seems like a fine way to inline.

  3. If a non-final method is inlined, that seems like an illegal move. However, it's not: The VM just made a falsifiable assumption: "This method, while not final, is nevertheless not overridden by anything anywhere in any class loaded in the VM". If it is true right now, the method can be inlined, even if it might not be true later on.

  4. Any time new classes are loaded in (this boils down to: A new class is thrown at ClassLoader's native defineClass method), a check is done if this causes any assumptions to cease to be true. If that happens, the hotspotted versions of all methods that depend on this assumption are invalidated and will revert back to normal (slowish, more or less interpreted) operation. If they are still run a lot, they will be hotspotted again, this time keeping in mind that the method cannot easily be inlined anymore due to it being actually overridden.

  5. As I said, this is just how it works now. It might work differently in a future java version because none of this is guaranteed. It's also oversimplified; for example, I didn't mention annotations like @jdk.internal.HotSpotIntrinsicCandidate or @java.lang.invokeForceInline. It's not relevant and you should as a rule not be writing code with presupositions about how inlining works. The whole point is that you don't need to know.

huangapple
  • 本文由 发表于 2020年10月21日 20:34:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/64463641.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定