这是一个相当糟糕的主意finalizer
, 一般来说;毕竟,它被弃用是有原因的。我认为首先重要的是要了解这种特殊方法的机制开始工作,或者为什么需要两个周期实现终结器的对象消失了。总体想法是,这是非确定性的,很容易出错,并且您可能会遇到这种方法的意外问题。
清理某些东西的事实上的方法是使用try with resources
(via AutoCloseable
), 一样容易 :
CachedObject cached = new CachedObject...
try(cached) {
}
但这并不总是一个选择,就像你的情况一样,很可能是这样。我不知道你使用的是什么缓存,但我们内部使用我们自己的缓存,它实现了所谓的移除监听器(我们的实现很大程度上基于guava
加上我们自己的少量补充)。那么你的缓存可能有相同的吗?如果没有,也许你可以换一个可以的?
如果两者都没有选择,则有自 java-9 以来更清晰的 API。您可以阅读它,例如执行以下操作:
static class CachedObject implements AutoCloseable {
private final String instance;
private static final Map<String, String> MAP = new HashMap<>();
public CachedObject(String instance) {
this.instance = instance;
}
@Override
public void close() {
System.out.println("close called");
MAP.remove(instance);
}
}
然后尝试通过以下方式使用它:
private static final Cleaner CLEANER = Cleaner.create();
public static void main(String[] args) {
CachedObject first = new CachedObject("first");
CLEANER.register(first, first::close);
first = null;
gc();
System.out.println("Done");
}
static void gc(){
for(int i=0;i<3;++i){
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
System.gc();
}
}
容易,对吧?也错了。这apiNote
通过以下方式提到这一点:
仅当关联对象变为幻像可达后才会调用清理操作,因此重要的是,执行清理操作的对象不保存对该对象的引用
问题是Runnable
(在第二个参数中Cleaner::register
) 捕获first
,现在对其有强烈的引用。这意味着永远不会调用清洁操作。相反,我们可以直接遵循文档中的建议:
static class CachedObject implements AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
private static final Map<String, String> MAP = new HashMap<>();
private final InnerState innerState;
private final Cleaner.Cleanable cleanable;
public CachedObject(String instance) {
innerState = new InnerState(instance);
this.cleanable = CLEANER.register(this, innerState);
MAP.put(instance, instance);
}
static class InnerState implements Runnable {
private final String instance;
public InnerState(String instance) {
this.instance = instance;
}
@Override
public void run() {
System.out.println("run called");
MAP.remove(instance);
}
}
@Override
public void close() {
System.out.println("close called");
cleanable.clean();
}
}
代码看起来有点复杂,但实际上并没有那么复杂。我们主要想做两件事:
- 将清理代码分离到一个单独的类中
- 并且该类必须没有对我们正在注册的对象的引用。这是通过没有参考文献来实现的
InnerState
to CachedObject
并且还做到了static
.
所以,我们可以测试一下:
public static void main(String[] args) {
CachedObject first = new CachedObject("first");
first = null;
gc();
System.out.println("Done");
System.out.println("Size = " + CachedObject.MAP.size());
}
static void gc() {
for(int i=0;i<3;++i){
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
System.gc();
}
}
这将输出:
run called
Done
Size = 0