Appearance
Java 的 Cleaner 机制
1. Cleaner
在 Java 9 中引入了 Cleaner 用于取代 finalize 方法,它允许我们为一组对象定义清理操作。
Cleaner 是借由 Cleanable 接口实现的,在 Cleanable 内部关联了一个 Runnable 对象。每个 Cleanable 在一个专用线程中运行。清理操作中抛出的所有异常都会被忽略。
最佳实践方式是,在对象关闭或不再需要时显式调用 clean 方法。
Caution:需要注意的是,清理操作不能引用正在注册的对象。如果引用了该对象,则该对象将无法变为虚引用,也将导致清理操作无法被自动调用。所以,不要使用内部类来实现清理操作,因为内部类隐式地持有外部对象的引用,这将阻止它被垃圾回收。
2. 从官方示例开始
Oracle 文档中提供的一个示例实现参考:
Java
public class CleaningExample implements AutoCloseable {
// A cleaner, preferably one shared within a library
private static final Cleaner cleaner = <cleaner>;
static class State implements Runnable {
State(...) {
// initialize State needed for cleaning action
}
public void run() {
// cleanup action accessing State, executed at most once
}
}
private final State;
private final Cleaner.Cleanable cleanable;
public CleaningExample() {
this.state = new State(...);
this.cleanable = cleaner.register(this, state);
}
public void close() {
cleanable.clean();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
请注意 close() 方法:
- 我们可以在应用程序代码中显式调用此方法来触发资源清理过程;
- 如果开发人员没有显式调用
close()方法,JVM 将自动调用该方法;
Tip:每个
Cleaner都会产生一个线程,因此建议只为整个应用程序或库创建一个Cleaner。
Note:
CleaningExample类实现了AutoCloseable接口,因此我们也可以在 try-with-resources 语句中使用此类。
3. Cleaner 实现资源清理的过程
可以使用其静态方法创建 Cleaner 对象,如下所示。此方法创建一个 Cleaner 实例并启动一个守护线程,该线程持续监视符合垃圾收集条件的对象:
Java
Cleaner cleaner = Cleaner.create();接下来,我们需要使用 register 方法注册对象和清理操作。该方法包含两个参数:
- 清洁器持续监视垃圾收集的对象;
- 一个
java.lang.Runnable实例,它表示要执行的清理操作;
Java
cleaner.register(object, runnable);最后,我们可以自己调用 clean 方法,或者等待 GC 调用它。clean 方法会调用清理操作并取消注册 runnable。
4. 一个示例
Resource.java:
Java
public class Resource {
//Demo resource
}1
2
3
2
3
由于线程创建的开销,每个应用程序应该只有一个 Cleaner 实例,因此我们在 utility 类中创建 Cleaner:
Java
import java.lang.ref.Cleaner;
public class AppCleanerProvider {
private static final Cleaner CLEANER = Cleaner.create();
public static Cleaner getCleaner() {
return CLEANER;
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
现在我们需要编写一个类,该类将能够访问 Resource 和 Cleaner。我们希望当 ClassAccessingResource 被垃圾收集时,应该调用 cleanResourceAction() 方法来释放资源。
ClassAccessingResource 还实现了 AutoCloseable 接口,以方便我们可以使用 try-with-resources 语句,这是可选的。我们也可以手动编写 close() 方法并自己调用它。
Java
import java.lang.ref.Cleaner;
public class ClassAccessingResource implements AutoCloseable {
private final Cleaner cleaner = AppCleanerProvider.getCleaner();
private final Cleaner.Cleanable cleanable;
//This resource needs to be cleaned after usage
private final Resource resource;
public ClassAccessingResource() {
this.resource = new Resource();
this.cleanable = cleaner.register(this, cleanResourceAction(resource));
}
public void businessOperation() {
//Access the resource in methods
System.out.println("Inside businessOperation()");
}
public void anotherBusinessOperation() {
//Access the resource in methods
System.out.println("Inside anotherBusinessOperation()");
}
@Override
public void close() throws Exception {
cleanable.clean();
}
private static Runnable cleanResourceAction(final Resource resource) {
return () -> {
// Perform cleanup actions
// resource.release();
System.out.println("Resource Cleaned Up !!");
};
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
为了演示资源清理,我们创建了两个 ClassAccessingResource 实例,并分别以显式和隐式两种方式调用了清理器:
Java
public class CleanerExample {
public static void main(final String[] args) throws Exception {
//1 Implicit Cleanup
try (final ClassAccessingResource clazzInstance = new ClassAccessingResource()) {
// Safely use the resource
clazzInstance.businessOperation();
clazzInstance.anotherBusinessOperation();
}
//2 Explicit Cleanup
final ClassAccessingResource clazzInstance = new ClassAccessingResource();
clazzInstance.businessOperation();
clazzInstance.anotherBusinessOperation();
clazzInstance.close();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在这两种方式中,资源清理都会触发一次:
Text
Inside businessOperation()
Inside anotherBusinessOperation()
Resource Cleaned Up !!
Inside businessOperation()
Inside anotherBusinessOperation()
Resource Cleaned Up !!1
2
3
4
5
6
7
2
3
4
5
6
7