封闭实例不符合垃圾收集条件的解决方法
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 之后有资格进行垃圾回收。
请注意,还有使用 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)
.
创建
只要封闭实例还活着,这里的封闭实例就没有资格进行垃圾回收,对吗?
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 之后有资格进行垃圾回收。
请注意,还有使用 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)
.