简体   繁体   English

Java 8闭包存储在哪里?

[英]Where are java 8 closures stored?

I wrote this class that can be used to build Arrays of type T using a builder pattern, storing the values inside closures until the array is actually constructed. 我编写了此类,该类可用于使用构建器模式构建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);
    }

}

This works quite well, but I was wondering where(stack?, heap?) the values are stored until build() gets called? 这工作得很好,但是我想知道值存储在哪里(堆栈?,堆?),直到build()被调用? Is there any reason this shouldn't be usable or performant? 有什么理由不应该使用或执行此操作吗? It does use reflections but that cost is paid only at the very end when build() is called. 它确实使用了反射,但是只有在调用build()时才支付费用。

Well, to be precise both heap and stack are involved in the lambda/closure construction process. 好吧,确切地说,lambda / closure构造过程涉及堆和栈。 To build the mind model of the closure you can think of it as creating an instance of a class for each lambda occurrence and passing to the constructor of that class all variables from the parent scopes which are accessed by the lambda. 要构建闭包的思维模型,您可以将其视为为每个lambda事件创建一个类的实例,并将该lambda访问的父作用域中的所有变量传递给该类的构造函数。 However, lets try to go through an example to see what exactly JVM does when building a closure for a 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());
        }

What this useless code does is actually building an instance of a lambda and anonymous inner class doing exactly same. 这个无用的代码实际上是在构建完全相同的lambda和匿名内部类的实例。 Now lets go through the corresponding byte code for both. 现在,让我们遍历两者的相应字节码。

Lambdas Lambda表达式

Below is the byte-code generated for the 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

Despite being written in Java byte-code the code above is quite self explaining : 尽管是用Java字节码编写的,但是上面的代码还是很容易解释的:

ALOAD 1
    ALOAD 2

These two commands push the references methodPairIntegerValue and methodIntegerValue to the stack (here is were stack part comes in). 这两个命令将对methodPairIntegerValuemethodIntegerValue的引用推入堆栈(这里是堆栈部分的进来)。 This is followed by the INVOKEDYNAMIC command. 随后是INVOKEDYNAMIC命令。 This command is the main differentiating factor of the lambdas from the anonymous inner classes. 此命令是lambda与匿名内部类的主要区别因素。 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. 如果对于匿名内部类,以字节代码生成的显式新类(对于lambda),则将实际实现推迟到应用程序运行时。 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). 但是,大多数现代JVM在发现INVOKEDYNAMICINVOKEDYNAMIC生成一个新类,该类具有两个属性,这些属性捕获在INVOKEDYNAMIC之前推入堆栈的INVOKEDYNAMIC并创建它的新实例(此处会增加额外的堆使用)。 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. 值得一提的是,这些行动不直接进行INVOKEDYNAMIC而是通过LambdaMetafactory到该呼叫被委托给。 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). 因此,最终输出与匿名内部类的输出非常相似(JVM将来可以自由更改由LambdaMetafactory合并的实现细节)。



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

This is a static method containing actual code of the lambda expression. 这是一个静态方法,其中包含lambda表达式的实际代码。 It is going to be invoked by a class LambdaMetafactory generates during the INVOKEDYNAMIC call. LambdaMetafactoryLambdaMetafactoryINVOKEDYNAMIC调用期间生成的INVOKEDYNAMIC调用它。 As you see what it does is pulling 2 values from the stack and performing actual summation. 正如您所看到的,它是从堆栈中提取2个值并执行实际求和。

Anonymous classes 匿名班

Bellow is the byte-code for the usage of the anonymous class, things are simpler here, hence I have added only the initiation part of the anonymous class and omitted the byte-code for the actual class: 波纹管是用于匿名类的字节码,这里的事情比较简单,因此我只添加了匿名类的启动部分,而省略了实际类的字节码:



    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

What the code does is pushing the values of this , methodPairIntegerValue , methodIntegerValue to the stack and invoking the constructor of a anonymous class which captures these values in the fields of the anonymous class. 代码要做的是将thismethodPairIntegerValuemethodIntegerValue的值推入堆栈,并调用匿名类的构造函数,该构造函数在匿名类的字段中捕获这些值。

As it is seen from the code snippets above memory footprint wise lambdas and anonymous inner classes are quite similar. 从内存占用量以上的代码片段可以看出,明智的lambda和匿名内部类非常相似。

Summary 摘要

Coming back to your question : The references used in the closure are passed around using the stack . 回到您的问题:闭包中使用的引用是使用堆栈传递的。 The instance of the generated anonymous class together with its fields holding the references of the variables used in the closure are stored in the heap (as it would if you would explicitly use a class instead of lambda and pass the values via a constructor). 生成的匿名类的实例及其字段(包含用于关闭的变量的引用的字段)存储在堆中 (就像您显式使用类而不是lambda并通过构造函数传递值那样)。

However, there is some difference in the performance of lambdas and anonymous inner classes with regards the bootstrap process and JIT. 但是,就引导过程和JIT而言,lambda和匿名内部类的性能有所不同。 Following links cover the topic in a great detail: 以下链接详细介绍了该主题:

Hope this helps (despite the answer being a bit lengthy) 希望这会有所帮助(尽管答案有点冗长)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM