Java中有析构函数吗?

2023/06/09

1. 概述

在这个简短的教程中,我们介绍在Java中销毁对象的可能性。

2. Java中的析构函数

每次我们创建一个对象时,Java都会自动在堆上分配内存。同样,每当不再需要某个对象时,内存将自动被释放。

在像C这样的语言中,当我们在内存中使用完一个对象时,我们必须手动释放它。然后,Java不支持手动内存释放。此外,Java编程语言的一个特性是自己处理对象销毁:通过一种称为垃圾收集的技术。

3. 垃圾收集

垃圾收集从堆上的内存中删除未使用的对象,它有助于防止内存泄漏。简单地说,当不再有对特定对象的引用并且该对象不再可访问时,垃圾收集器将该对象标记为不可访问并回收其空间。

未能正确处理垃圾收集可能会导致性能问题,并最终导致应用程序内存不足。

当对象达到程序中不再可访问的状态时,可以对对象进行垃圾回收。当出现以下两种情况之一时,对象不再可达(访问):

  • 该对象没有任何指向它的引用
  • 对该对象的所有引用都超出范围

Java提供System.gc()方法来帮助支持垃圾收集,通过调用这个方法,我们可以建议JVM运行垃圾收集器。但是,我们不能保证JVM会真正调用它,JVM可以自由地忽略该请求。

4. Finalizer

Object类提供finalize()方法,在垃圾收集器从内存中删除对象之前,它会调用finalize()方法。该方法可以运行0次或1次。但是,它不能为同一个对象运行2次。

Object类中定义的finalize()方法不执行任何特殊操作。

finalize的主要目标是在对象从内存中删除之前释放它使用的资源。例如,我们可以重写该方法来关闭数据库连接或其他资源。

让我们创建一个包含BufferedReader实例变量的类:

class Resource {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }
}

在我们的例子中,我们没有关闭资源。我们可以在finalize()方法中关闭reader:

@Override
protected void finalize() {
    try {
        reader.close();
    } catch (IOException e) {
        // ...
    }
}

当JVM调用finalize()方法时,BufferedReader资源将被释放。finalize()方法抛出的异常将停止对象的终结。

但是,从Java 9开始,finalize()方法已被弃用,使用finalize()方法可能会令人困惑并且难以正确使用。

如果我们想释放对象持有的资源,我们应该考虑实现AutoCloseable接口。像Cleaner和PhantomReference这样的类提供了一种更灵活的方式来在对象变得无法访问时管理资源。

4.1 实现AutoCloseable

AutoCloseable接口提供close()方法,该方法将在退出try-with-resources块时自动执行。在这个方法中,我们可以关闭一个对象使用的资源。

让我们修改Resource类以实现AutoCloseable接口:

class Resource implements AutoCloseable {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }

    @Override
    public void close() throws Exception {
        reader.close();
    }
}

我们可以使用close()方法来关闭reader资源,而不是使用finalize()方法。

4.2 Cleaner类

如果我们想在对象变为幻像可访问时执行特定操作,我们可以使用Cleaner类。换句话说,当一个对象完成并且它的内存准备好被释放时。

现在,让我们看看如何使用Cleaner类。首先定义Cleaner:

Cleaner cleaner = Cleaner.create();

接下来,我们创建一个包含Cleaner引用的类:

class Order implements AutoCloseable {

    private final Cleaner cleaner;

    public Order(Cleaner cleaner) {
        this.cleaner = cleaner;
    }
}

其次,我们将在Order类中定义一个实现Runnable的静态内部类:

static class CleaningAction implements Runnable {

    private final int id;

    public CleaningAction(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.printf("Object with id %s is garbage collected. %n", id);
    }
}

CleaningAction的实例表示清理操作,我们应该注册每个清理操作,以便它们在对象变为幻像可访问后运行。

我们应该考虑不使用lambda进行清理操作。因为如果使用lambda,我们可以轻松地捕获对象引用,从而防止对象变为幻像可达。如上所述,使用静态嵌套类将避免保留对象引用。

让我们在Order类中添加Cleanable实例变量:

private Cleaner.Cleanable cleanable;

Cleanable实例表示包含清理操作的Cleaner对象。

接下来,我们创建一个注册清理操作的方法:

public void register(Product product, int id) {
    this.cleanable = cleaner.register(product, new CleaningAction(id));
}

最后,让我们实现close()方法:

public void close() {
    cleanable.clean();
}

clean()方法注销可清理对象并调用已注册的清理操作。无论调用多少次clean,这个方法最多会被调用一次。

当我们在try-with-resources块中使用CleaningExample实例时,close()方法会调用清理操作:

final Cleaner cleaner = Cleaner.create();
try (Order order = new Order(cleaner)) {
	for (int i = 0; i < 10; i++) {
		order.register(new Product(i), i);
	}
} catch (Exception e) {
	System.err.println("Error: " + e);
}

在其他情况下,当实例变为幻像可访问时,cleaner将调用clean()方法。

此外,在System.exit()期间清理程序的行为是特定于实现的,Java不保证是否会调用清理操作。

5. 总结

在这个简短的教程中,我们研究了Java中对象销毁的可能性。综上所述,Java不支持手动对象销毁。但是,我们可以使用finalize()或Cleaner来释放对象持有的资源。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章