A Guide to the finalize Method in Java – Java中finalize方法的指南

最后修改: 2018年 1月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll focus on a core aspect of the Java language – the finalize method provided by the root Object class.

在本教程中,我们将重点讨论Java语言的一个核心方面–由根Object类提供的finalize方法。

Simply put, this is called before the garbage collection for a particular object.

简单地说,这是在对某一特定对象进行垃圾收集之前调用的。

2. Using Finalizers

2.使用终结者

The finalize() method is called the finalizer.

finalize()方法被称为终结者。

Finalizers get invoked when JVM figures out that this particular instance should be garbage collected. Such a finalizer may perform any operations, including bringing the object back to life.

当JVM发现这个特定的实例应该被垃圾回收时,就会调用终结者。这样的终结者可以执行任何操作,包括让对象起死回生。

The main purpose of a finalizer is, however, to release resources used by objects before they’re removed from the memory. A finalizer can work as the primary mechanism for clean-up operations, or as a safety net when other methods fail.

然而,最终处理程序的主要目的是在对象从内存中删除之前释放它们所使用的资源。终结器可以作为清理操作的主要机制,也可以作为其他方法失败时的安全网。

To understand how a finalizer works, let’s take a look at a class declaration:

为了理解finalizer是如何工作的,我们来看看一个类的声明。

public class Finalizable {
    private BufferedReader reader;

    public Finalizable() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        this.reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

    // other class members
}

The class Finalizable has a field reader, which references a closeable resource. When an object is created from this class, it constructs a new BufferedReader instance reading from a file in the classpath.

Finalizable有一个字段reader,它引用一个可关闭的资源。当从这个类中创建一个对象时,它会构造一个新的BufferedReader实例,从classpath中的一个文件读取。

Such an instance is used in the readFirstLine method to extract the first line in the given file. Notice that the reader isn’t closed in the given code.

这样一个实例被用于readFirstLine方法,以提取给定文件中的第一行。注意,在给定的代码中,阅读器并没有关闭。

We can do that using a finalizer:

我们可以用一个终结者来做到这一点。

@Override
public void finalize() {
    try {
        reader.close();
        System.out.println("Closed BufferedReader in the finalizer");
    } catch (IOException e) {
        // ...
    }
}

It’s easy to see that a finalizer is declared just like any normal instance method.

很容易看出,finalizer的声明就像任何正常的实例方法一样。

In reality, the time at which the garbage collector calls finalizers is dependent on the JVM’s implementation and the system’s conditions, which are out of our control.

在现实中,垃圾收集器调用终结者的时间取决于JVM的实现和系统的条件,这些都是我们无法控制的。

To make garbage collection happen on the spot, we’ll take advantage of the System.gc method. In real-world systems, we should never invoke that explicitly, for a number of reasons:

为了让垃圾回收在现场发生,我们将利用System.gc方法。在现实世界的系统中,由于一些原因,我们不应该明确调用该方法。

  1. It’s costly
  2. It doesn’t trigger the garbage collection immediately – it’s just a hint for the JVM to start GC
  3. JVM knows better when GC needs to be called

If we need to force GC, we can use jconsole for that.

如果我们需要强制GC,我们可以使用jconsole来实现。

The following is a test case demonstrating the operation of a finalizer:

下面是一个测试案例,演示了一个终结者的操作。

@Test
public void whenGC_thenFinalizerExecuted() throws IOException {
    String firstLine = new Finalizable().readFirstLine();
    assertEquals("baeldung.com", firstLine);
    System.gc();
}

In the first statement, a Finalizable object is created, then its readFirstLine method is called. This object isn’t assigned to any variable, hence it’s eligible for garbage collection when the System.gc method is invoked.

在第一条语句中,创建了一个Finalizable对象,然后调用其readFirstLine方法。这个对象没有分配给任何变量,因此当调用System.gc方法时,它就有资格进行垃圾回收。

The assertion in the test verifies the content of the input file and is used just to prove that our custom class works as expected.

测试中的断言验证了输入文件的内容,只是用来证明我们的自定义类按预期工作。

When we run the provided test, a message will be printed on the console about the buffered reader being closed in the finalizer. This implies the finalize method was called and it has cleaned up the resource.

当我们运行所提供的测试时,在控制台中会打印出一条关于缓冲的阅读器在最终处理程序中被关闭的信息。这意味着finalize方法被调用,它已经清理了资源。

Up to this point, finalizers look like a great way for pre-destroy operations. However, that’s not quite true.

到此为止,终结者看起来是预销毁操作的一个好方法。然而,这并不完全正确。

In the next section, we’ll see why using them should be avoided.

在下一节中,我们将看到为什么要避免使用它们。

3. Avoiding Finalizers

3.避免使用终结者

Despite the benefits they bring in, finalizers come with many drawbacks.

尽管它们带来了好处,但终结者也有许多缺点。

3.1. Disadvantages of Finalizers

3.1.终结者的劣势

Let’s have a look at several problems we’ll be facing when using finalizers to perform critical actions.

让我们来看看在使用终结者来执行关键动作时,我们会遇到的几个问题。

The first noticeable issue is the lack of promptness. We cannot know when a finalizer runs since garbage collection may occur anytime.

第一个明显的问题是缺乏及时性。我们无法知道终结者何时运行,因为垃圾收集可能随时发生。

By itself, this isn’t a problem because the finalizer still executes, sooner or later. However, system resources aren’t unlimited. Thus, we may run out of resources before a clean-up happens, which may result in a system crash.

就其本身而言,这并不是一个问题,因为终结者仍然在执行,迟早的事。然而,系统资源并不是无限的。因此,我们可能会在清理发生之前耗尽资源,这可能导致系统崩溃。

Finalizers also have an impact on the program’s portability. Since the garbage collection algorithm is JVM implementation-dependent, a program may run very well on one system while behaving differently on another.

终结器对程序的可移植性也有影响。由于垃圾收集算法与JVM的实现有关,一个程序可能在一个系统上运行得很好,而在另一个系统上的表现却不同。

The performance cost is another significant issue that comes with finalizers. Specifically, JVM must perform many more operations when constructing and destroying objects containing a non-empty finalizer.

性能成本是终结者带来的另一个重要问题。具体来说,JVM在构造和销毁包含非空终结者的对象时必须执行更多的操作

The last problem we’ll be talking about is the lack of exception handling during finalization. If a finalizer throws an exception, the finalization process stops, leaving the object in a corrupted state without any notification.

我们要讨论的最后一个问题是在最终化过程中缺乏对异常的处理。如果终结者抛出一个异常,终结过程就会停止,使对象处于损坏的状态而没有任何通知。

3.2. Demonstration of Finalizers’ Effects

3.2.终结者的效果展示

It’s time to put the theory aside and see the effects of finalizers in practice.

现在是时候把理论放在一边,看看终结者在实践中的效果了。

Let’s define a new class with a non-empty finalizer:

让我们定义一个具有非空终结器的新类。

public class CrashedFinalizable {
    public static void main(String[] args) throws ReflectiveOperationException {
        for (int i = 0; ; i++) {
            new CrashedFinalizable();
            // other code
        }
    }

    @Override
    protected void finalize() {
        System.out.print("");
    }
}

Notice the finalize() method – it just prints an empty string to the console. If this method were completely empty, the JVM would treat the object as if it didn’t have a finalizer. Therefore, we need to provide finalize() with an implementation, which does almost nothing in this case.

请注意 finalize() 方法 – 它只是向控制台打印一个空字符串。如果这个方法完全是空的,JVM将把这个对象当作没有最终处理程序的对象。因此,我们需要为finalize()提供一个实现,在这种情况下它几乎什么都不做。

Inside the main method, a new CrashedFinalizable instance is created in each iteration of the for loop. This instance isn’t assigned to any variable, hence eligible for garbage collection.

main方法中,一个新的CrashedFinalizable实例在for循环的每次迭代中被创建。这个实例并没有分配给任何变量,因此有资格进行垃圾回收。

Let’s add a few statements at the line marked with // other code to see how many objects exist in the memory at runtime:

让我们在标有//其他代码的一行添加一些语句,看看在运行时内存中存在多少对象。

if ((i % 1_000_000) == 0) {
    Class<?> finalizerClass = Class.forName("java.lang.ref.Finalizer");
    Field queueStaticField = finalizerClass.getDeclaredField("queue");
    queueStaticField.setAccessible(true);
    ReferenceQueue<Object> referenceQueue = (ReferenceQueue) queueStaticField.get(null);

    Field queueLengthField = ReferenceQueue.class.getDeclaredField("queueLength");
    queueLengthField.setAccessible(true);
    long queueLength = (long) queueLengthField.get(referenceQueue);
    System.out.format("There are %d references in the queue%n", queueLength);
}

The given statements access some fields in internal JVM classes and print out the number of object references after every million iterations.

给出的语句访问JVM内部类中的一些字段,并在每百万次迭代后打印出对象引用的数量。

Let’s start the program by executing the main method. We may expect it to run indefinitely, but that’s not the case. After a few minutes, we should see the system crash with an error similar to this:

让我们通过执行main方法来启动该程序。我们可能期望它能无限期地运行,但事实并非如此。几分钟后,我们应该看到系统崩溃,出现类似这样的错误。

...
There are 21914844 references in the queue
There are 22858923 references in the queue
There are 24202629 references in the queue
There are 24621725 references in the queue
There are 25410983 references in the queue
There are 26231621 references in the queue
There are 26975913 references in the queue
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.ref.Finalizer.register(Finalizer.java:91)
    at java.lang.Object.<init>(Object.java:37)
    at com.baeldung.finalize.CrashedFinalizable.<init>(CrashedFinalizable.java:6)
    at com.baeldung.finalize.CrashedFinalizable.main(CrashedFinalizable.java:9)

Process finished with exit code 1

Looks like the garbage collector didn’t do its job well – the number of objects kept increasing until the system crashed.

看起来垃圾收集器没有做好它的工作–对象的数量不断增加,直到系统崩溃。

If we removed the finalizer, the number of references would usually be 0 and the program would keep running forever.

如果我们去掉终结器,引用的数量通常为0,程序将永远运行下去。

3.3. Explanation

3.3.解释

To understand why the garbage collector didn’t discard objects as it should, we need to look at how the JVM works internally.

为了理解为什么垃圾收集器没有按规定丢弃对象,我们需要看一下JVM的内部工作方式。

When creating an object, also called a referent, that has a finalizer, the JVM creates an accompanying reference object of type java.lang.ref.Finalizer. After the referent is ready for garbage collection, the JVM marks the reference object as ready for processing and puts it into a reference queue.

当创建一个具有终结器的对象(也称为引用)时,JVM会创建一个附带的java.lang.ref.Finalizer类型的引用对象。在引用对象准备好进行垃圾回收后,JVM会将引用对象标记为准备好进行处理,并将其放入引用队列。

We can access this queue via the static field queue in the java.lang.ref.Finalizer class.

我们可以通过java.lang.ref.Finalizer类中的静态字段queue访问这个队列。

Meanwhile, a special daemon thread called Finalizer keeps running and looks for objects in the reference queue. When it finds one, it removes the reference object from the queue and calls the finalizer on the referent.

同时,一个名为Finalizer的特殊守护线程持续运行,在引用队列中寻找对象。当它找到一个对象时,它会从队列中移除引用对象,并在引用对象上调用终结者。

During the next garbage collection cycle, the referent will be discarded – when it’s no longer referenced from a reference object.

在下一个垃圾收集周期中,引用将被丢弃–当它不再被引用对象引用时。

If a thread keeps producing objects at a high speed, which is what happened in our example, the Finalizer thread cannot keep up. Eventually, the memory won’t be able to store all the objects, and we end up with an OutOfMemoryError.

如果一个线程一直在高速生产对象,这就是我们的例子中发生的情况,Finalizer线程无法跟上。最终,内存将无法存储所有的对象,而我们最终会出现OutOfMemoryError

Notice a situation where objects are created at warp speed as shown in this section doesn’t often happen in real life. However, it demonstrates an important point – finalizers are very expensive.

请注意,本节中显示的对象以曲率速度创建的情况在现实生活中并不经常发生。然而,它展示了一个重要的观点–终结者是非常昂贵的。

4. No-Finalizer Example

4.无终结者的例子

Let’s explore a solution providing the same functionality but without the use of finalize() method. Notice that the example below isn’t the only way to replace finalizers.

让我们来探讨一个提供相同功能但不使用finalize()方法的解决方案。请注意,下面的例子并不是取代finalizer的唯一方法。

Instead, it’s used to demonstrate an important point: there are always options that help us to avoid finalizers.

相反,它被用来证明一个重要的观点:总是有一些选项可以帮助我们避免终结者。

Here’s the declaration of our new class:

下面是我们新类的声明。

public class CloseableResource implements AutoCloseable {
    private BufferedReader reader;

    public CloseableResource() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

    @Override
    public void close() {
        try {
            reader.close();
            System.out.println("Closed BufferedReader in the close method");
        } catch (IOException e) {
            // handle exception
        }
    }
}

It’s not hard to see that the only difference between the new CloseableResource class and our previous Finalizable class is the implementation of the AutoCloseable interface instead of a finalizer definition.

不难看出,新的CloseableResource类与我们之前的Finalizable类的唯一区别是实现了AutoCloseable接口而不是定义了finalizer。

Notice that the body of the close method of CloseableResource is almost the same as the body of the finalizer in class Finalizable.

请注意,CloseableResourceclose方法的主体与Finalizable类中finalizer的主体几乎相同。

The following is a test method, which reads an input file and releases the resource after finishing its job:

下面是一个测试方法,它读取一个输入文件并在完成工作后释放资源。

@Test
public void whenTryWResourcesExits_thenResourceClosed() throws IOException {
    try (CloseableResource resource = new CloseableResource()) {
        String firstLine = resource.readFirstLine();
        assertEquals("baeldung.com", firstLine);
    }
}

In the above test, a CloseableResource instance is created in the try block of a try-with-resources statement, hence that resource is automatically closed when the try-with-resources block completes execution.

在上面的测试中,一个CloseableResource实例在try-with-resources语句的try块中被创建,因此当try-with-resources块执行完毕时,该资源被自动关闭。

Running the given test method, we’ll see a message printed out from the close method of the CloseableResource class.

运行给定的测试方法,我们会看到从Close类的CloseableResource方法中打印出来的一条信息。

5. Conclusion

5 结语

In this tutorial, we focused on a core concept in Java – the finalize method. This looks useful on paper but can have ugly side effects at runtime. And, more importantly, there’s always an alternative solution to using a finalizer.

在本教程中,我们重点讨论了Java的一个核心概念–finalize方法。这在纸面上看起来很有用,但在运行时可能会产生丑陋的副作用。而且,更重要的是,除了使用finalizer,总有其他的解决方案。

One critical point to notice is that finalize has been deprecated starting with Java 9 – and will eventually be removed.

需要注意的一个关键点是,finalize已经从Java 9开始被弃用,并且最终会被删除。

As always, the source code for this tutorial can be found over on GitHub.

一如既往,本教程的源代码可以在GitHub上找到