Lambda捕获的引用是否保留在其在捕获之前所在的堆栈中?

huangapple go评论77阅读模式
英文:

Are the references captured by a lambda kept in the stack where they where present before capture?

问题

如果在方法执行的时候,无论是通过声明还是通过方法参数从前一个相邻的方法栈中传递的值,引用都被存储在栈中。

当引用被 lambda 表达式捕获时,如果 lambda 方法体在将来的某个时间执行,它的前一个相邻方法栈(执行 lambda 方法的方法)很可能会忽略引用所指向的位置(堆中的对象)。

在一些关于引用的教程中,暗示当一个对象的字段(实例变量)是另一个对象时,引用直接存储在堆中,就在对象本身内部。

问题在于函数式接口是不可变的,所以假设有一个存储引用的字段是不可能的。

现在,让我们假设有以下类:

public class ReferenceSupplier<T> implements Supplier<T> {
    
    private Supplier<T> supplier;
    
    @SuppressWarnings("unchecked")
    public ReferenceSupplier() {
        supplier = () -> (T) new Object();
    }
    
    public ReferenceSupplier(Supplier<T> supplier) {
        this.supplier = supplier;
    }
    
    @Override
    public T get() {
        return supplier.get();
    }
    
    public void set(Supplier<T> value) {
        supplier = value;
    }
}

假设 set 方法在现场创建了一个匿名的 Supplier,目的是捕获某个引用(我知道它可以用于更有用的事情,在这种情况下,Supplier 函数有点浪费):

private final ReferenceSupplier<Consumer<Boolean>> attacher = new ReferenceSupplier<>();

private final BiConsumer<U, BiConsumer<T, U>> attacherBuilder = (observer, acceptor) -> {
    Consumer<Boolean> attacherRef = attached -> {
        U tempObserver;
        if (attached) {
            tempObserver = observer;
            state.setValue(() -> State.active);
        } else {
            tempObserver = null;
            state.setValue(() -> State.inactive);
        }
        isAttached.set(attached);
        dispatcherBuilder.accept(tempObserver, acceptor);
    };
    attacher.set(() -> attacherRef);
};

attachRef 是对堆中 Consumer 对象的引用。
当在 attacher.set() 方法中创建一个新的 Supplier 时,一个新的 Supplier 对象被存储在堆中。
然后将对匿名 Supplier 对象的引用(这需要另一个问题来解释...)按值传递给 ReferenceSupplier 类的 setter,这意味着 ReferenceSupplier 类将不会直接持有 Consumer 引用,而是将引用传递给堆中的不可变 Supplier 对象,该对象充当代理,而不是指向堆中 Consumer 对象的引用(与可变类中的字段实例引用所做的方式类似),而是指向在栈中的引用("attachRef"),该引用指向堆中的 Consumer 对象。

这是否阻止了发生这种情况的栈被弹出?还是 lambda 的方法体 () -> attacherRef 及其捕获的引用被存储在其他地方?在哪里存储?

英文:

If references are stored in the stack at the moment of method execution either it be by declaration or by a value being passed by their previous neighboring method stack via method parameter.

Where are they being stored, when references are captured by a lambda expression if the method body of the lambda will be executed some time in the future and it's neighboring previous method stack (the one executing the lambda method) will most likely ignore the location of the referent (object in the heap)??.

In some tutorials explaining reference, it is implied, that when an Object has another object as a field (an instance variable), the reference is stored directly in the Heap within the object itself.

The problem is that functional interfaces are immutable, so assuming that there is some field storing the reference is impossible.

Now: let's assume the next class:

public class ReferenceSupplier&lt;T&gt; implements Supplier&lt;T&gt; {

    private Supplier&lt;T&gt; supplier;

    @SuppressWarnings(&quot;unchecked&quot;)
    public ReferenceSupplier() {
        supplier = () -&gt; (T) new Object();
    }

    public ReferenceSupplier(Supplier&lt;T&gt; supplier) {
        this.supplier = supplier;
    }

    @Override
    public T get() {
        return supplier.get();
    }

    public void set(Supplier&lt;T&gt; value) {
        supplier = value;
    }
}

lets say the set method creates an annonymous Supplier on site, with the aim of capturing some reference (I'm aware that It could be used for more useful things, in this case the Supplier function is somewhat wasted.)

private final ReferenceSupplier&lt;Consumer&lt;Boolean&gt;&gt; attacher = new ReferenceSupplier&lt;&gt;();

private final BiConsumer&lt;U, BiConsumer&lt;T, U&gt;&gt; attacherBuilder = (observer, acceptor) -&gt; {
    Consumer&lt;Boolean&gt; attacherRef = attached -&gt; {
        U tempObserver;
        if (attached) {
            tempObserver = observer;
            state.setValue(() -&gt; State.active);
        } else {
            tempObserver = null;
            state.setValue(() -&gt; State.inactive);
        }
        isAttached.set(attached);
        dispatcherBuilder.accept(tempObserver, acceptor);
    };
    attacher.set(() -&gt; attacherRef);
};

The attachRef is a reference to a Consumer object in the Heap.
When a new Supplier is created in the attacher.set() method, a new Supplier Object is stored in the Heap.
Then the reference to the anonymous Supplier object (this needs another question in itself...) is passed by value to the ReferenceSupplier<T> class setter, this means that the ReferenceSupplier class will not be holding the Consumer reference directly, instead it will be holding the reference to an immutable Supplier Object in the Heap that serves as a proxy, NOT to the Consumer object in the Heap (like field instances references would do in mutable classes), but instead to the reference in the Stack ("attachRef") that references the Consumer object in the Heap.

Does this prevents the Stack where this is happening (method body of the attacherBuilder biConsumer lambda) from being popped? OR is the method body of the lambda () -&gt; attacherRef including it's captured reference stored somewhere else? Where?

答案1

得分: 1

如果有什么东西会“阻止堆栈弹出”,那么方法将无法返回;要返回的位置也在堆栈上。您可以查看堆栈,但如果您返回,那么您要返回的内容将覆盖您存储在那里的内容,使我们回到:您无法阻止堆栈弹出,因此使用这种逆向逻辑,显然无法阻止堆栈弹出。

只有以下内容可以存在于堆栈上:

  • 基本类型
  • 引用(我在很大程度上进行了简化,但可以这样说:64位内存指针,指向对象存储在堆内存中的起始位置。情况比这复杂,但作为堆栈内容的心理模型,这个解释有效)。
  • 执行指针(在调用方法时随时存储;用于从方法返回,Java代码根本无法访问它们;如果您尝试篡改类文件以访问它们,验证器将拒绝加载类文件)。

这意味着 ReferenceSupplier 类将不会直接持有 Consumer 引用,而是将持有对堆中不可变 Supplier 对象的引用,该对象充当代理,而不是堆中的 Consumer 对象(就像字段实例引用会在可变类中所做的那样),而是代替堆栈中的引用("attachRef"),该引用引用堆中的 Consumer 对象。

如果在堆上有一个反过来引用其他东西的对象,那么该引用因此成为该对象的字段,因此也会位于堆上。

英文:

If something would 'prevent the stack from being popped', then a method cannot return; the place to return to is also on the stack. You could peek at the stack but if you return then what you return to will overwrite what you stored there, getting us back to: You cannot prevent the stack from being popped, so using this reverse logic, clearly the stack is not prevented from being popped.

The only things that can live on the stack are:

  • Primitives
  • References (I'm oversimplifying a lot, but let's say: 64-bit memory pointers that point to the start, in heap memory, of an object's storage. It's more complicated than that, but for a mental model of what is on the stack, this works).
  • execution pointers (stored anytime you invoke a method; these are used to return from methods and are not accessible by java code at all; if you try to hack a class file to get at them, the verifier will refuse to load the class file).

> this means that the ReferenceSupplier class will not be holding the Consumer reference directly, instead it will be holding the reference to an immutable Supplier Object in the Heap that serves as a proxy, NOT to the Consumer object in the Heap (like field instances references would do in mutable classes), but instead to the reference in the Stack ("attachRef") that references the Consumer object in the Heap.

If you have an object on-heap that in turn references to something else, that reference is therefore a field of that object, and thus, also on-heap.

huangapple
  • 本文由 发表于 2020年10月24日 10:05:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/64509278.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定