Method inlining in JVM

Kirill Rybkin2022-04-11

What method inlining is?

Inlining is optimization that the calling method is replaced with the body of the called method. When the calling method JVM should allocate the stack frame in the stack, after method invocation return to the calling method. Inlining increases performance by avoiding overhead for allocating stack frame.

Stack memory

Which flags control inlining?

-XX:+PrintFlagsFinal - shows active flags while starting the application.

We will be interested in some of these:

  • CompileThreshold - number of method invocations, before the method is considered "hot".
  • MaxInlineLevel - defines a method called chain constraint. Default value 9.
  • MaxInlineSize - defines max a method size in bytecode for inlining. Default value 35. If the method is not "hot" and its bytecode size is more than 35 bytes, then it will not be inlining.
  • FreqInlineSize - defines max "hot" method size. Default value depends on the platform, on my laptop 325 bytes.
  • How does JIT consider a method for inlining?

    JIT compiler tries to perform inlining based on two things:
  • How often a method was invoked.
  • Which a method size.

The main decision about a method will be inlined, depends on its size and how "hottest" it is. JVM considers that the method is hot, based on its internal logic. There aren't configurable options that directly control this process. If the method is considered for inlining, cause it was often invoked, it will be inlined only if its bytecode size is less than 325 bytes. On the other hand, it was considered for inlining only if its bytecode size was less than 35 bytes.

The JIT inlines static, private, or final methods in general. And while public methods are also candidates for inlining, not every public method will necessarily be inlined. The JVM needs to determine that there's only a single implementation of such a method.

Example of inlining

Consider listing below.

public class App {
    public static void main(String[] args) {
        long upto = Long.parseLong(args[0]);

        for(int i = 0; i < upto; i++) {
            int x = inline1();
        }
    }

    public static int inline1() {
        return inline2();
    }

    public static int inline2() {
        int x = 3;
        int y = inline3() + x;
        return y;
    }

    public static int inline3() {
        return inline4();
    }

    public static int inline4() {
        return 3;
    }
}
mvn verify
java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -cp target/inlining-1.0-SNAPSHOT.jar com.rkdeep.App 10000

Compilation message

  1. "callee is too large" - the message is printed by compiler C1, when the called method size over then VM param -XX:MaxInlineSize=35.
  2. "too big" и "hot method too big" - the message is printed by compiler С2, when the called method size over then MaxInlineSize (35) or FreqInlineSize (325) correspondingly. Thus both messages mean the same thing, but on different compilation tiers.

Execution result:

    130   90       3       com.rkdeep.App::inline2 (4 bytes)
                              @ 0   com.rkdeep.App::inline3 (4 bytes)   inline
                                @ 0   com.rkdeep.App::inline4 (2 bytes)   inline
    130   88       1       com.rkdeep.App::inline4 (2 bytes)
    130   89       3       com.rkdeep.App::inline1 (4 bytes)
                              @ 0   com.rkdeep.App::inline2 (4 bytes)   inline
                                @ 0   com.rkdeep.App::inline3    130   92       4       com.rkdeep.App::inline2 (4 bytes)
 (4 bytes)   inline
                                  @ 0   com.rkdeep.App::inline4 (2 bytes)   inline
    130   91       2       com.rkdeep.App::inline3 (4 bytes)
                              @ 0   com.rkdeep.App::inline4 (2 bytes)   inline
    131   90       3       com.rkdeep.App::inline2 (4 bytes)   made not entrant
                              @ 0   com.rkdeep.App::inline3 (4 bytes)   inline (hot)
                                @ 0   com.rkdeep.App::inline4 (2 bytes)   inline (hot)
    131   93       4       com.rkdeep.App::inline1 (4 bytes)
    131   89       3       com.rkdeep.App::inline1 (4 bytes)   made not entrant
                              @ 0   com.rkdeep.App::inline2 (4 bytes)   inline (hot)
                                @ 0   com.rkdeep.App::inline3 (4 bytes)   inline (hot)
                                  @ 0   com.rkdeep.App::inline4 (2 bytes)   inline (hot)

Reduce compilation limit by adding the flag -XX:FreqInlineSize=8

    118   95 %     4       com.rkdeep.App::main @ 9 (28 bytes)
    118   93 %     3       com.rkdeep.App::main @ 9 (28 bytes)   made not entrant
                              @ 16   com.rkdeep.App::inline1 (4 bytes)   inline (hot)
                                @ 0   com.rkdeep.App::inline2 (10 bytes)   too big

As we can see, the method inline2 with the comment "too big" because we set compilation limit 8 bytes.

Sometimes there are recommendations that suggest increasing the value of the MaxInlineSize flag. Often miss one aspect if the value MaxInlineSize is more than 35, which means the method will be inlined in the first invocation. However, if the method is invoked often, in that case, its performance became more important and it will be inlined later. Therefore, the flag MaxInlineSize can reduce the warmup time, needed for tests, but it has little impact on applications that run for a long time.

References

  1. "Optimizing Java: Practical Techniques for Improving JVM Application Performance" Benjamin J Evans, James Gough, Chris Newland.
  2. "Java Performance : In-depth Advice for Tuning and Programming Java 8, 11, and Beyond" Scott Oaks.
  3. "Java HotSpot VM Options"
Language: enua