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

时间:2017-08-27 06:17:51

标签: 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.

使用此处避免使用的lambda和流的原因是什么?可能的影响是什么?

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

1 个答案:

答案 0 :(得分:10)

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

JDK中的

invokedynamic机制相当复杂。它涉及许多与方法句柄,Lambda Metafactories等相关的java.lang.invoke.*类,需要加载和初始化。此外,链接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();
    }
}

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

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

完整输出为here。我们可以看到,Inner需要136 ms和537个加载类,而Lambda需要161 ms和620个加载类。

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

修改

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

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