避免在 VM 启动时使用的类使用 lambda 和流



通过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 和流的原因是什么,它们可能产生的影响是什么?

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

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

JDK中的invokedynamic机器相当复杂。它涉及许多与方法句柄、Lambda 元工厂等相关的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

内.log

[0.011s][info][class,load] opened: C:Program FilesJavajdk-9libmodules
[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

拉姆达.log

[0.011s][info][class,load] opened: C:Program FilesJavajdk-9libmodules
[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

完整的输出在这里。如我们所见,Inner需要 136 毫秒和 537 个加载类,而Lambda需要 161 毫秒和 620 个加载类。

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

编辑

我所描述的开销由两部分组成:

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

最新更新