java 8闭包存储在哪里?

Rau*_*orn 2 java closures java-8

我编写了这个类,可以使用构建器模式构建类型为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);
    }

}
Run Code Online (Sandbox Code Playgroud)

这很好用,但我想知道在调用build()之前存储值的位置(堆栈?,堆?)?是否有任何理由不应该使用或执行?它确实使用了反射,但只有在调用build()时才支付成本.

Rub*_*ben 5

好吧,准确地说,堆和堆栈都涉及lambda/closure构造过程.要构建闭包的思维模型,您可以将其视为为每个lambda事件创建一个类的实例,并将所有来自lambda访问的父作用域的变量传递给该类的构造函数.但是,让我们试着通过一个例子来看看为构建lambda的闭包时JVM究竟做了什么:



    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());
        }

Run Code Online (Sandbox Code Playgroud)

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

Lambda表达式

下面是为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

Run Code Online (Sandbox Code Playgroud)

尽管用Java字节码编写,但上面的代码非常自我解释:


    ALOAD 1
    ALOAD 2

这两个命令将引用methodPairIntegerValuemethodIntegerValue堆栈推送(这里是堆栈部分).接下来是INVOKEDYNAMIC命令.此命令是来自匿名内部类的lambda的主要区别因素.如果对于匿名内部类,在字节码中生成一个显式的新类,对于lambdas,实际的实现被推迟到应用程序的运行时.但是,大多数现代JVM在发现时会INVOKEDYNAMIC生成一个新类,该类具有两个属性,用于捕获在此之前推送到堆栈的值INVOKEDYNAMIC并创建它的新实例(此处有额外的堆使用量跳入).值得一提的是,这些操作不是由委托直接执行,INVOKEDYNAMIC而是由LambdaMetafactory委托给其执行.因此,最终输出与匿名内部类非常相似(JVM可以自由地更改LambdaMetafactory将来合并的实现细节).



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

Run Code Online (Sandbox Code Playgroud)

这是一个静态方法,包含lambda表达式的实际代码.它将由调用LambdaMetafactory期间生成的类INVOKEDYNAMIC调用.正如您所看到的那样,从堆栈中提取2个值并执行实际求和.

匿名课程

Bellow是使用匿名类的字节码,这里的事情比较简单,因此我只添加了匿名类的启动部分,省略了实际类的字节码:



    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

Run Code Online (Sandbox Code Playgroud)

什么代码确实是推的值this,methodPairIntegerValue,methodIntegerValue堆栈,并援引了在匿名类的字段捕获这些值的匿名类的构造函数.

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

摘要

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

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

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