Java 8 lambda闭包和垃圾回收机制

5

我对Java 8的'闭包'有些困惑。据说它可以封闭值。考虑以下类。

public class SomeClassWithLargeMemoryFootprint {
     //some state
     private SomeObject someObj;
     //some more state

     public void doSomething(SomeAsyncHelper helper) {
         helper.doAsync( () -> {
                             //some super slow operation
                             int foo = someObj.whatever();
                             //some more stuff    
                       });
     }
}

.

//Let's assume SomeAsyncHelper.doAsync takes a VoidRunner that looks like below
interface VoidRunner {
    void apply();
}

问题是,当异步帮助程序仍在工作时,SomeClassWithLargeMemoryFootprint的实例是否可以被GC删除? 对我来说,很明显“someObj”不能被GC删除,因为它是doSomething()中lambda所需的。 其余状态怎么样?
此外,请考虑以下变体,其中我们调用包含类的成员方法:
public class SomeClassWithLargeMemoryFootprint {
     //some state
     private SomeObject someObj;
     //some more state

     public void doSomething(SomeAsyncHelper helper) {
         helper.doAsync( () -> {
                //do something
                memberMethod();
                //do something else
                });
     }

     private void memberMethod() {
         //do something
     }

}

现在是怎样的情况?'helper'如何知道如何执行"memberMethod"?它会获取到SomeClassWithLargeMemoryFootprint实例的引用吗?GC序列会是什么样子?


据我所知,Java 中的 GC 使用引用计数,而 Java 中的 lambda 只是一些内联类的语法糖。 长话短说:它的工作方式就好像您使用内联类实现所有这些功能一样。 由于内联类是私有类,因此您始终可以访问周围的对象(及其属性和方法)。 引用计数负责处理其余部分以及正确释放对象(即在 lambda 执行后)。 - Turing85
这确实是一个重复的问题,但是与另一个问题不同: Java8 Lambda是否像匿名类一样保留对其封闭实例的引用?。另请参阅https://dev59.com/smAg5IYBdhLWcg3wFHuF#23991339。 - maaartinus
1个回答

3

当异步帮助程序仍在工作时,SomeClassWithLargeMemoryFootprint的实例是否可以被GC回收?

不行。VoidRunner实例具有对其封闭SomeClassWithLargeMemoryFootprint对象的强引用,并且由异步任务执行器某种方式引用。因此它不能被GC回收。

剩下的状态怎么办?

剩余状态由SomeClassWithLargeMemoryFootprint的实例引用,因此无法被GC回收。

“helper”如何知道如何执行“memberMethod”?

与第一个示例中访问someObj(即this.someObj)的方式相同:它具有对其封闭SomeClassWithLargeMemoryFootprint对象(即this)的引用。


太酷了,谢谢!匿名类持有对封闭类的引用这一事实很清晰明了。 - SvS
一个后续问题:当我在lambda中没有访问任何封闭类成员时,是否有一种方法可以打破对封闭类的强引用?我的意思是一种简洁的方式,不要太偏离使用lambda的便利性。不是实现lambda接口的具体类。 - SvS
@Saivivekh Swaminathan:当您不访问周围实例的成员时,lambda表达式将不会捕获对周围实例的引用。在这种情况下,没有必要“打破强引用”。在您的第一个变体中,您仅调用someObj上的方法,可以通过首先将目标对象读入局部变量SomeObject localVar = this.someObj;,并仅在lambda中使用局部变量:helper.doAsync(() -> { … int foo = localVar.whatever(); … }); 仅通过localVar捕获SomeObject实例。 - Holger
@Holger:感谢您的回复。这意味着声称lambda只是内联匿名类的语法糖的人是错误的,对吗?根据https://dev59.com/Tm445IYBdhLWcg3wE2Ne,匿名内部类显然总是会得到对封闭类的引用。 - SvS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接