2

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?

梁雨生
  • 385
  • 3
  • 16

2 Answers2

8

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

apangin
  • 92,924
  • 10
  • 193
  • 247
5

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.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Thank you very much for your answer.I'm new to the JIT, although my question seems a bit stupid, but I am just very curious about its principle. – 梁雨生 Oct 21 '20 at 15:22
  • 2
    Addendum: even if a non-final method has been overridden, the JIT may inline the method at a particular call site under the assumption that this particular call site still always ends up at the original method, of course, with a fast pre-check that will deoptimize when the assumption does not hold anymore. So the loading of a subclass does not necessarily invalidate all inlined code, it may just introduce the necessity of inserting more checks. More than often, the subclass is only used at some places whereas most of the already running code doesn’t change its behavior. – Holger Oct 21 '20 at 15:48