gpt4 book ai didi

java - Java 8 闭包存储在哪里?

转载 作者:行者123 更新时间:2023-11-30 06:06:46 24 4
gpt4 key购买 nike

我编写了此类,可用于使用构建器模式构建类型 T 的数组,将值存储在闭包中,直到实际构建数组为止。

public class ArrayBuilder<T> {

final Class<T> type;

public ArrayBuilder(Class<T> type){
this.type = type;
}

private Supplier<Supplier<Store>> start = () -> {
final Store element = new Store(-1, null, null);
return () -> element;
};

private class Store {
final Integer index;
final T val;
final Supplier<Store> getNextVal;

public Store(Integer index, T val, Supplier<Store> getNextVal) {
this.index = index;
this.val = val;
this.getNextVal = getNextVal;
}
}

private Supplier<Store> queue(Integer index, T value, Supplier<Store> next) {
final Store element = new Store(index, value, next);
return () -> element;
}

public ArrayBuilder<T> add(T element) {
Supplier<Store> currentStore = start.get();
Integer currentIndex = start.get().get().index + 1;
start = () -> queue(currentIndex, element, currentStore);
return this;
}

public T[] build() {
Store nextVal = start.get().get();
Integer size = nextVal.index + 1;
T[] result = makeGenericArray(size);
while (nextVal.index != -1) {
result[nextVal.index] = nextVal.val;
nextVal = nextVal.getNextVal.get();
}
return result;
}

private T[] makeGenericArray(Integer size) {
return (T[]) Array.newInstance(type, size);
}

}

这工作得很好,但我想知道在调用 build() 之前,这些值存储在哪里(堆栈?,堆?)?有什么理由这不应该可用或性能不佳吗?它确实使用了反射,但只有在最后调用 build() 时才会支付该费用。

最佳答案

嗯,准确地说,堆和栈都参与了 lambda/闭包构造过程。要构建闭包的思维模型,您可以将其视为为每个 lambda 事件创建一个类的实例,并将 lambda 访问的父作用域中的所有变量传递给该类的构造函数。但是,让我们尝试通过一个示例来了解 JVM 在为 lambda 构建闭包时到底做了什么:



public void performLambdasDemo() {
// Declare variables which are going to be used in the lambda closure
final Pair methodPairIntegerValue = new Pair(RandomUtils.nextInt(), RandomUtils.nextInt());
final Integer methodIntegerValue = RandomUtils.nextInt();
// Declare lambda
final Supplier methodSupplierLambda = () -> {
return methodPairIntegerValue.fst + 9000 + methodIntegerValue.intValue();
};
// Declare anonymous class
final Supplier methodSupplierInnerClass = new Supplier() {
@Override
public Integer get() {
return methodPairIntegerValue.fst + 9001 + methodIntegerValue.intValue();
}
};
System.out.println(methodSupplierLambda.get());
System.out.println(methodSupplierInnerClass.get());
}

这段无用的代码所做的实际上是构建一个 lambda 和匿名内部类的实例,其作用完全相同。现在让我们看一下两者的相应字节码。

Lambdas

下面是为 lambda 生成的字节码:



L2
LINENUMBER 35 L2
ALOAD 1
ALOAD 2
INVOKEDYNAMIC get(Lcom/sun/tools/javac/util/Pair;Ljava/lang/Integer;)Ljava/util/function/Supplier; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
()Ljava/lang/Object;,
// handle kind 0x6 : INVOKESTATIC
com/sfl/stackoverflow/LambdasExperiment.lambda$performLambdasDemo$1(Lcom/sun/tools/javac/util/Pair;Ljava/lang/Integer;)Ljava/lang/Integer;,
()Ljava/lang/Integer;
]
ASTORE 3
L3
// Omit quite some byte-code and jump to the method declaration
// access flags 0x100A
private static synthetic lambda$performLambdasDemo$1(Lcom/sun/tools/javac/util/Pair;Ljava/lang/Integer;)Ljava/lang/Integer;
L0
LINENUMBER 36 L0
ALOAD 0
GETFIELD com/sun/tools/javac/util/Pair.fst : Ljava/lang/Object;
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
SIPUSH 9000
IADD
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
IADD
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ARETURN
MAXSTACK = 2
MAXLOCALS = 2

尽管是用 Java 字节码编写的,但上面的代码很容易 self 解释:

    ALOAD 1    ALOAD 2

These two commands push the references methodPairIntegerValue and methodIntegerValue to the stack (here is were stack part comes in). This is followed by the INVOKEDYNAMIC command. This command is the main differentiating factor of the lambdas from the anonymous inner classes. If for the anonymous inner classes an explicit new class in generated in the byte-code, for lambdas the actual implementation is postponed till the runtime of the application. However, most modern JVMs when spotting INVOKEDYNAMIC generate a new class which has two properties capturing the values pushed to the stack prior the INVOKEDYNAMIC and create a new instance of it (and here where extra heap usage jumps in). It is worth mentioning that these actions are not directly performed by the INVOKEDYNAMIC but rather by LambdaMetafactory to which the call is being delegated to. So the end output is quite similar as it would have been for the anonymous inner class (JVMs are free to change this implementation detail incorporated by LambdaMetafactory in the future).



private static synthetic lambda$performLambdasDemo$1(Lcom/sun/tools/javac/util/Pair;Ljava/lang/Integer;)Ljava/lang/Integer;

这是一个静态方法,包含 lambda 表达式的实际代码。它将由 LambdaMetafactoryINVOKEDYNAMIC 调用期间生成的类调用。如您所见,它所做的是从堆栈中提取 2 个值并执行实际求和。

匿名类

下面是匿名类的使用字节码,这里比较简单,所以我只添加了匿名类的初始化部分,省略了实际类的字节码:



L3
LINENUMBER 39 L3
NEW com/sfl/stackoverflow/LambdasExperiment$2
DUP
ALOAD 0
ALOAD 1
ALOAD 2
INVOKESPECIAL com/sfl/stackoverflow/LambdasExperiment$2. (Lcom/sfl/stackoverflow/LambdasExperiment;Lcom/sun/tools/javac/util/Pair;Ljava/lang/Integer;)V
ASTORE 4
L4

代码所做的是将 thismethodPairIntegerValuemethodIntegerValue 的值压入堆栈并调用匿名类的构造函数在匿名类的字段中捕获这些值。

从上面的代码片段可以看出,内存占用明智的 lambda 和匿名内部类非常相似。

总结

回到你的问题:闭包中使用的引用使用堆栈 传递。生成的匿名类的实例及其保存闭包中使用的变量引用的字段存储在中(如果您显式使用类而不是 lambda 并传递通过构造函数的值)。

但是,在引导过程和 JIT 方面,lambda 和匿名内部类的性能存在一些差异。以下链接详细介绍了该主题:

希望这会有所帮助(尽管答案有点冗长)

关于java - Java 8 闭包存储在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43430682/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com