概述

每当我们在Java中创建一个对象时,系统会在堆上自动分配内存。同样地,当一个对象不再需要时,内存会自动被释放。在像C这样的语言中,当我们使用完内存中的对象后,需要手动释放。然而,Java并不支持手动内存释放。相反,Java编程语言的一个特性是通过一种称为垃圾回收(garbage collection, GC)的技术来处理对象的销毁。

2. Java中的析构函数

Java不支持手动内存管理,它通过垃圾回收机制自动管理对象的生命周期。当一个对象不再有引用指向它,或者所有引用超出了作用域,垃圾收集器就会标记这个对象为不可达并回收其内存空间。

如果处理不当,垃圾回收可能导致性能问题,甚至导致应用耗尽内存。当一个对象在程序中不再可访问时,它可能会被垃圾回收,这种情况发生在以下两种情况之一:

  • 对象没有任何引用指向它
  • 所有指向该对象的引用都已超出作用域

Java提供了System.gc()方法来辅助垃圾回收。调用此方法可以建议JVM运行垃圾收集器,但并不能保证JVM一定会执行。JVM有权忽略请求。

3. 垃圾回收

垃圾回收从堆内存中移除不再使用的对象,有助于防止内存泄漏。简单来说,当一个对象没有了对它的任何引用且无法访问时,垃圾收集器会标记它为不可达并回收其空间。

4. 最终析构方法(Finalizer)

Object类提供了finalize()方法。在垃圾回收器将对象从内存中删除之前,它会调用finalize()方法。这个方法可能执行零次或一次,但同一个对象不会执行两次。Object类内部的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();
    }
}

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

不过,从Java 9开始,finalize()方法已被弃用。使用finalize()方法可能会造成混淆,并难以正确使用。

如果我们想释放对象持有的资源,应考虑实现AutoCloseable接口。CleanerPhantomReference等类提供了一种更灵活的方式来管理对象变得不可达后的资源。

4.1. 实现AutoCloseable

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

让我们修改示例类以实现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()方法来关闭资源,而不是使用finalize()方法。

4.2. Cleaner

如果我们希望在对象变为幽灵可达(即即将被垃圾回收)时执行特定操作,可以使用Cleaner类。换句话说,当对象被最终化并且内存准备被释放时。

现在来看看如何使用Cleaner类。首先,定义Cleaner

Cleaner cleaner = Cleaner.create();

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

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);
    }
}

内部类的实例代表清理动作。为了使它们在对象变为幽灵可达后运行,我们需要为每个清理动作注册。

我们应该避免在清理动作中使用lambda表达式,因为这可能会意外地捕获对象引用,阻止对象变为幽灵可达。使用静态嵌套类,如上述所示,可以避免保留对象引用。

Order类中添加Cleanable实例变量:

private Cleaner.Cleanable cleanable;

Cleanable实例代表包含清理动作的清理对象。

接下来,创建一个方法来注册清理动作:

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

最后,实现close()方法:

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

clean()方法会注销cleanable并调用已注册的清理动作。无论调用多少次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);
}

在其他情况下,当实例变为幽灵可达时,清理器会调用clean()方法。

此外,CleanerSystem.exit()期间的行为取决于具体实现。Java并不保证清理动作是否会被执行。

5. 总结

Java通过垃圾回收机制自动管理内存,避免手动内存管理的复杂性。虽然最终析构方法(finalize())曾被用于资源清理,但自Java 9起已被弃用,推荐使用AutoCloseable接口和Cleaner类来更好地处理对象生命周期结束时的资源清理。这样可以确保代码的可读性和可靠性。