封闭实例不符合垃圾收集条件的解决方法

Work-around for enclosing instance ineligible for garbage collection

只要封闭实例还活着,这里的封闭实例就没有资格进行垃圾回收,对吗?

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        return new Foo<>() {
            T result = null;

            boolean cached = false;

            @Override
            public T compute() {
                if (!cached) {
                    result = Foo.this.compute();
                    cached = true;
                }
                return result;
            }
        };
    }
}

这能解决那个问题吗?

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        return Foo.memoize(this);
    }

    private static <T> Foo<T> memoize(Foo<T> original) {
        class Bar implements Foo<T> {
            T result = null;

            Foo<T> foo;

            Bar(Foo<T> original) {
                foo = original;
            }

            @Override
            public T compute() {
                if (foo != null) {
                    result = foo.compute();
                    foo = null;
                }
                return result;
            }
        }
        return new Bar(original);
    }
}

不,为什么会这样?

memoize() 函数中的 Bar 实例有一个包含 Foo 对象的字段。无论您是通过范围界定还是通过隐式字段分配来持有它,都与垃圾收集无关。

如果要保留对可被垃圾回收的对象的引用,请使用 WeakReference/SoftReference

在第一种情况下,匿名内部 class 'captures' 通过使用 Foo.this 对封闭 Foo 的引用。由于匿名 class 无限期保留此引用,因此在匿名 class 也符合条件之前,封闭的 Foo 不符合垃圾回收条件。

在第二个示例中,此引用在 compute 被调用一次后被丢弃,因此封闭的 class 之后有资格进行垃圾回收。

Accessing Members of an Enclosing Class

请注意,还有使用 lambda 表达式的替代方法,因为 lambda 表达式仅根据需要捕获值,例如

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        AtomicReference<Foo<T>> holder = new AtomicReference<>();
        holder.set(() -> {
               T result = compute();
               holder.set(() -> result);
               return result;
            }
        );
        return () -> holder.get().compute();
    }
}

holder 最初包含一个实现的 lambda Foo<T>,它引用了原始 Foo<T> 实例,将在第一次评估时调用 compute,但随后, 它将用一个新的 lambda 实现 Foo<T> 替换自己,它总是 return 计算值。新的 lambda 甚至不需要评估条件,因为已经暗示了值已经计算的事实。

请注意,holder 对象不必是 AtomicReference,但没有规范的简单易用替代方案。无法创建通用数组,因此我想到的唯一替代方案是大小为 1 的(可变)列表,例如通过 Arrays.asList(null).

创建