02_oo_16_finalization

第二十九章 对象释放过程(Object Finalization)

1 简介

在Java语言中,当对象使用完毕后,对象是由垃圾回收机制自动销毁的。开发人员也可通过调用System.gc()或者Runtime.gc()触发垃圾回收过程。在对象被回收之前,Java虚拟机会调用Object.finalize()方法。在该方法中,开发人员可以处理一些自定义的回收工作。本章主要介绍finalize方法以及一些注意事项。

2 Finalize成员方法

Finalize方法是Object类的成员方法。Finalize成员方法被定义为protected意味着任何类都可自定义finalize()函数,并覆盖(Override) Object类中提供的默认处理过程。但是,finalize成员方法只由Java虚拟机调用,准确的说是垃圾回收器(Garbage Collector)调用,因此,任何Java代码不能直接调用finalize方法。

在Object类中提供的finalize成员方法并不会做任何处理,它只是提供了一个接口,供垃圾回收器调用。然而,开发人员可以覆盖(Override) finalize成员方法,处理一些额外的清理工作。finalize方法何时会被调用依赖于Java虚拟机的实现,Java语言并没有加以限制。换句话说,当一个对象使用完毕后,并不一定会马上被释放,而是需要等到Java虚拟机认为的“合适”的时候。这个时机是依Java虚拟机的实现或者版本变化而变化的。只有当垃圾回收器准备回收对象时,才会调用该对象的fianlize成员方法。

由哪个线程调用finalize成员方法也是依赖于Java虚拟机的实现的。Java语言给予了Java虚拟机充分的自由,finalize成员方法可由垃圾回收器所在的线程调用,也可由另一个线程调用,也可以由其他多个线程并发调用。

finalize成员方法调用的顺序也是依赖于Java虚拟机的实现。给定两个对象,即使这两个对象有着引用依赖关系,即使这两个对象转变成“可释放状态”有着先后顺序,但是,垃圾回收器可以以任意的顺序调用它们的finalize成员方法,或者使用两个线程同时调用它们。因此,开发人员不能根据依赖关系或者其他因素假设finalize成员方法的调用顺序。

但是,Java虚拟机可以确保的是,对于一个对象finalize方法只被调用一次。在调用后,不会再对该对象有任何处理,直到该对象被回收。如果在finalize方法中发生了异常或者错误,finalize函数的运行会立即结束,发生的异常会被丢弃。

3 Finalize成员方法的错误使用方法

在C++程序中,析构函数(Destructor)常常被用于清理和释放资源(例如,数据库连接)。然而,Java语言中的Finalize成员方法与C++语言中的析构函数是不同的。Finalize成员方法的运行机制导致了其不能用于清理和释放某些资源,因为,finalize方法被调用的时机是不确定的。如下例所示,DBConnection是一个包装类(Wrapper Class)。它封装了数据库的连接Connection。DBConnection类在establishConnection方法中申请了一个新的连接,在finalize方法中释放连接。

这个Java例子的实现是错误的。最主要的原因是finalize方法的调用时机是无法确定的。即使DBConnection对象已经使用完毕,成为可回收对象,但是,Java虚拟机并不一定会立刻回收该对象。如果有许多DBConnection对象未被回收的话,这些对象实际上还是占用着数据库的连接,这可导致无法再次建立新的数据库连接。

在本例中,数据库连接只是一个示范例子,还有许多类似的资源不能使用finalize方法释放,例如TCP连接等。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;

public class DBConnection {
    private Connection conn = null;
    
    public DBConnection(String data) {
    }
    
    public void establishConnection(String url, String user, String password) 
        throws SQLException, SQLTimeoutException {
        this.conn = DriverManager.getConnection(url, user, password);
    }
    
    @Override
    protected void finalize() throws Throwable {
        // 因为finalize方法被调用的时机是不确定的。即使对象已处于“可回收状态”,
        // 但该对象仍然保持着数据库的连接,这可能导致数据库连接耗尽的问题。
        if (this.conn != null)
            this.conn.close();
    }
}

 

4 结语

本章介绍了finalize方法和其相关的注意事项。因为finalize方法被调用的时机完全由Java虚拟机的实现决定。所以,finalize方法不能用于清理和释放有限的资源,例如数据库连接、网络连接,文件句柄(File Handler)等。读者在使用finalize方法时需格外小心,finalize被调用的时机、运行于哪个线程,被调用的顺序均是由Java虚拟机决定的。

 

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.