Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding lambda and stream usage for a class used at VM Startup

Going through the java.lang.module I read amongst a class documentation the following:

@implNote ... is used at VM startup and so deliberately
avoids using lambda and stream usages in code paths used during
startup.

What are the causes from using lambda and streams that are avoided here and what are their possible impacts?

Illustrations would help understand better, not looking for opinions here though.

like image 770
Naman Avatar asked Aug 27 '17 06:08

Naman


1 Answers

Not relying on lambdas and streams (that extensively use lambdas) helps to avoid doing redundant work at VM bootstrap. That in turn reduces startup time and memory footprint.

invokedynamic machinery in JDK is rather complex. It involves many java.lang.invoke.* classes related to Method Handles, Lambda Metafactories etc. that need to be loaded and initialized. Furthermore, to link invokedynamic bytecode JVM dynamically creates an adapter using ObjectWeb ASM framework. Generating such classes in runtime also takes time and space.

Let's measure an overhead of using lambda instead of inner class in a very basic scenario. I create two similar classes that do nothing but instantiating either an inner class or a lambda:

class Inner {
    public static void main(String[] args) {
        Runnable r = new Runnable() { public void run() {} };
        r.run();
    }
}

class Lambda {
    public static void main(String[] args) {
        Runnable r = () -> {};
        r.run();
    }
}

Then I run both with class loading log turned on:

java -Xlog:class+load:file=inner.log Inner
java -Xlog:class+load:file=lambda.log Lambda

inner.log

[0.011s][info][class,load] opened: C:\Program Files\Java\jdk-9\lib\modules
[0.022s][info][class,load] java.lang.Object source: jrt:/java.base
[0.022s][info][class,load] java.io.Serializable source: jrt:/java.base
...
[0.136s][info][class,load] Inner$1 source: file:/C:/Andrei/
[0.136s][info][class,load] java.lang.Shutdown source: jrt:/java.base
[0.136s][info][class,load] java.lang.Shutdown$Lock source: jrt:/java.base

lambda.log

[0.011s][info][class,load] opened: C:\Program Files\Java\jdk-9\lib\modules
[0.022s][info][class,load] java.lang.Object source: jrt:/java.base
[0.022s][info][class,load] java.io.Serializable source: jrt:/java.base
...
[0.159s][info][class,load] Lambda$$Lambda$1/1282788025 source: Lambda
[0.159s][info][class,load] java.lang.invoke.InnerClassLambdaMetafactory$1 source: jrt:/java.base
[0.159s][info][class,load] java.lang.invoke.MethodHandleImpl$IntrinsicMethodHandle source: jrt:/java.base
[0.159s][info][class,load] java.lang.invoke.SimpleMethodHandle source: jrt:/java.base
[0.159s][info][class,load] sun.invoke.util.Wrapper$1 source: jrt:/java.base
[0.160s][info][class,load] java.lang.invoke.LambdaForm$MH/100555887 source: java.lang.invoke.LambdaForm
[0.160s][info][class,load] java.lang.invoke.LambdaForm$MH/1983747920 source: java.lang.invoke.LambdaForm
[0.160s][info][class,load] java.lang.Shutdown source: jrt:/java.base
[0.161s][info][class,load] java.lang.Shutdown$Lock source: jrt:/java.base

The full output is here. As we can see, Inner required 136 ms and 537 loaded classes, while Lambda took 161 ms and 620 loaded classes.

So, in this simple example avoiding a single lambda helped to save 25 ms of startup time with 83 less classes loaded.

EDIT

The overhead I've described consists of two parts:

  1. Loading and initializing java.lang.invoke.* classes - this is the constant part which needs to be done only once.
  2. Linking particular lambda call site - this requires invocation of LambdaMetafactory bootstrap method and generating runtime adapter for calling target method. This needs to be done for every lambda, so this part of overhead is proportional to the number of lambdas in the code.
like image 149
apangin Avatar answered Sep 18 '22 20:09

apangin