避免在VM Startup中使用的类的lambda和流使用

Nam*_*man 6 java lambda jvm startup java-9

通过java.lang.module我在类文档中阅读以下内容:

@implNote ... is used at VM startup and so deliberately
avoids using lambda and stream usages in code paths used during
startup.
Run Code Online (Sandbox Code Playgroud)

使用lambda和流的原因是什么,以及它们可能产生的影响是什么?

插图有助于更好地理解,而不是在这里寻找意见.

apa*_*gin 10

不依赖于lambda和流(广泛使用lambdas)有助于避免在VM引导程序中执行冗余工作.这反过来又减少了启动时间和内存占用.

invokedynamicJDK中的机器相当复杂.它涉及许多java.lang.invoke.*与方法句柄,Lambda Metafactories等相关的类,需要加载和初始化.此外,链接invokedynamic字节码JVM使用ObjectWeb ASM框架动态创建适配器.在运行时生成这样的类也需要时间和空间.

让我们衡量在一个非常基本的场景中使用lambda而不是内部类的开销.我创建两个类似的类,除了实例化内部类或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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我打开两个类加载日志:

java -Xlog:class+load:file=inner.log Inner
java -Xlog:class+load:file=lambda.log Lambda
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

完整输出在这里.我们可以看到,Inner需要136 ms和537个加载类,同时Lambda需要161 ms和620个加载类.

因此,在这个简单的例子中,避免单个lambda有助于节省25毫秒的启动时间,减少了83个类的加载.

编辑

我所描述的开销包括两部分:

  1. 加载和初始化java.lang.invoke.*类 - 这是需要仅执行一次的常量部分.
  2. 链接特定的lambda调用站点 - 这需要调用LambdaMetafactory引导程序方法并生成用于调用目标方法的运行时适配器.这需要为每个lambda完成,因此这部分开销与代码中lambda的数量成正比.

  • @nullpointer我已经更新了答案.开销与lambda数量加上加载和初始化其他类所需的常量开销成正比. (3认同)