一文详解Java中的finalize()方法
作者:周杰伦fans
前言
Java的finalize()方法是Object类定义的一个特殊方法,主要用于在对象被垃圾回收器回收之前执行一些清理工作。下面我将从基本概念、工作原理、使用场景、注意事项以及示例代码等方面详细解释这个方法。
基本概念
finalize()方法是Java中Object类的一个protected方法,每个Java类都隐式继承了这个方法。它的基本语法如下:
protected void finalize() throws Throwable {
// 清理资源的代码
}
在Object类中,finalize()方法的默认实现是空的,即不做任何事情。子类可以重写这个方法来提供自己的清理逻辑。
工作原理
当垃圾回收器(GC)确定一个对象不再被任何引用指向时,它会被标记为可回收对象。在真正回收这个对象的内存之前,垃圾回收器会调用该对象的finalize()方法。这个过程可以简单描述为:
- 对象不再被任何引用指向,成为"不可达"状态
- 垃圾回收器标记该对象为可回收
- 在回收前,垃圾回收器调用对象的
finalize()方法 finalize()方法执行完毕后,对象被真正回收
需要强调的是,finalize()方法的调用时机是不确定的,它取决于垃圾回收器的实现和调度策略。垃圾回收器可能在任何时候决定调用finalize()方法,也可能永远不会调用它。
主要用途
1. 资源释放
这是finalize()方法最常见的用途。当对象持有一些外部资源(如文件句柄、数据库连接等)时,可以在finalize()方法中释放这些资源,以避免资源泄漏。
示例:
public class ResourceHolder {
private File file;
public ResourceHolder(String fileName) {
try {
file = new File(fileName); // 打开文件进行操作
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void finalize() throws Throwable {
try {
if (file != null) {
file.close(); // 在对象被回收前关闭文件
}
} finally {
super.finalize();
}
}
}
在这个例子中,ResourceHolder类持有一个文件对象。在finalize()方法中,我们检查文件是否不为空,并在对象被回收前关闭文件,以释放资源。
2. 对象状态重置
可以在finalize()方法中重置对象的状态,使其可以被再次使用。这对于对象池或缓存对象等场景非常有用,可以避免频繁地创建和销毁对象,提高程序的性能和效率。
示例:
public class CacheObject {
private int[] cache;
public CacheObject(int size) {
cache = new int[size]; // 初始化缓存数据
}
@Override
protected void finalize() throws Throwable {
// 清理缓存数据
for (int i = 0; i < cache.length; i++) {
cache[i] = 0;
}
cache = null;
super.finalize();
}
}
在这个例子中,CacheObject类持有一个整数数组作为缓存。在finalize()方法中,我们遍历缓存数组并将每个元素设置为0,然后将缓存数组设置为null,以完成清理工作。
3. 对象的自我清理
某些对象在被销毁之前需要进行一些特定的清理操作,例如清理内部缓存、重置对象状态等。finalize()方法可以用于实现这些自我清理逻辑。
示例:
class SelfCleaningObject {
private boolean isCleaned;
public void clean() {
isCleaned = true;
}
@Override
protected void finalize() throws Throwable {
if (!isCleaned) {
// 进行自我清理操作
System.out.println("Performing self-cleaning.");
isCleaned = true;
}
super.finalize();
}
}
在这个例子中,SelfCleaningObject类有一个isCleaned标志,用于表示对象是否已经被清理。在finalize()方法中,我们检查isCleaned标志,如果对象未被清理,则执行自我清理操作。
使用示例
下面是一个更完整的示例,演示了如何在Java中使用finalize()方法进行资源清理:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FinalizeExample {
private FileWriter fileWriter;
public FinalizeExample() {
try {
File file = new File("data.txt");
fileWriter = new FileWriter(file);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void finalize() throws Throwable {
try {
if (fileWriter != null) {
fileWriter.close();
System.out.println("File resources cleaned up in finalize().");
}
} finally {
super.finalize();
}
}
public static void main(String[] args) {
FinalizeExample obj = new FinalizeExample();
System.out.println("Object created with hash code: " + obj.hashCode());
obj = null; // 切断引用,使对象成为垃圾
// 建议JVM执行垃圾回收
System.gc();
System.out.println("Requested garbage collection.");
// 等待一段时间,让垃圾回收有机会执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End of program.");
}
}
在这个示例中,我们创建了一个FinalizeExample对象,它持有一个FileWriter实例。在finalize()方法中,我们关闭了这个FileWriter。在main方法中,我们显式地断开对对象的引用,并调用System.gc()建议JVM执行垃圾回收。然后我们等待一段时间,让垃圾回收有机会执行。
注意事项
尽管finalize()方法提供了一种在对象被销毁之前进行清理的机制,但它存在一些重要的问题和限制,需要特别注意:
1. 调用不确定性
finalize()方法的调用时机是不确定的,它取决于垃圾回收器的实现和调度策略。这意味着:
- 不能保证
finalize()方法一定会被调用 - 不能保证
finalize()方法何时被调用 - 不能保证
finalize()方法的执行顺序
2. 性能开销
finalize()方法的调用会带来一定的性能开销,因为它需要在垃圾回收器的工作线程中执行。如果finalize()方法中包含大量的计算或I/O操作,可能会影响垃圾回收器的性能,甚至导致系统的性能下降。
3. 异常处理
finalize()方法中可能会抛出异常,但这些异常会被忽略,不会影响垃圾回收器的正常工作。因此,在finalize()方法中应该避免抛出异常,或者在抛出异常时进行适当的处理。
4. 对象复活
finalize()方法中有一个特殊的特性:它可以在方法内部创建对该对象的引用,从而使对象重新变为"可达"状态。这被称为"对象复活"。这种特性可能导致对象在垃圾回收后再次存活,从而延长对象的寿命,并可能导致一些难以预测的问题。
示例:
public class FinalizeResurrection {
public static FinalizeResurrection resurrectedObject = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
resurrectedObject = this; // 使对象复活
}
public static void main(String[] args) throws InterruptedException {
FinalizeResurrection obj = new FinalizeResurrection();
obj = null; // 切断引用
System.gc(); // 建议垃圾回收
// 等待finalize()方法执行
Thread.sleep(1000);
if (resurrectedObject != null) {
System.out.println("Object has been resurrected!");
} else {
System.out.println("Object has not been resurrected.");
}
resurrectedObject = null; // 再次切断引用
System.gc(); // 再次建议垃圾回收
Thread.sleep(1000);
if (resurrectedObject == null) {
System.out.println("Object is finally garbage collected.");
}
}
}
在这个例子中,FinalizeResurrection类在finalize()方法中将自己赋值给一个静态变量,从而使对象"复活"。第一次垃圾回收后,对象会复活;第二次垃圾回收后,对象才会真正被回收。
5. 并发问题
finalize()方法是在垃圾回收器线程中调用的,而不是在创建对象的线程中调用。这可能导致一些并发问题,特别是在对象状态依赖于创建线程上下文的情况下。
6. 内存泄漏风险
由于finalize()方法的调用是不确定的,过度依赖finalize()方法可能会导致内存泄漏。例如,如果在finalize()方法中创建了新的对象,并且这些对象没有被正确引用,那么它们将不会被垃圾回收,从而导致内存泄漏。
示例:
public class MemoryLeakExample {
private static final List<MemoryLeakExample> instances = new ArrayList<>();
public MemoryLeakExample() {
instances.add(this);
}
@Override
protected void finalize() throws Throwable {
instances.remove(this);
}
}
在这个例子中,MemoryLeakExample类的每个实例都会被添加到一个静态列表中。在finalize()方法中,我们从列表中删除了当前对象。然而,由于finalize()方法的调用是不确定的,可能会导致一些对象无法被正确删除,从而导致内存泄漏。
替代方案
由于finalize()方法存在上述问题和限制,Java社区推荐使用其他机制来管理资源释放和对象清理。以下是一些更好的替代方案:
1. try-with-resources语句
Java 7及更高版本引入了try-with-resources语句,它可以自动关闭实现了AutoCloseable接口的资源,而不需要显式调用finalize()方法。
示例:
public class TryWithResourcesExample {
public static void main(String[] args) {
try (FileWriter fileWriter = new FileWriter("data.txt")) {
fileWriter.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
}
// fileWriter会自动关闭,无需在finalize()中处理
}
}
在这个例子中,FileWriter实现了AutoCloseable接口,因此在try-with-resources块结束时,它会自动被关闭,无需依赖finalize()方法。
2. 显式资源释放
对于不适用try-with-resources的资源,可以在代码中显式地释放资源,通常使用try-finally块来确保资源被释放。
示例:
public class ExplicitResourceRelease {
public static void main(String[] args) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("data.txt");
fileWriter.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在这个例子中,我们使用try-finally块确保FileWriter被正确关闭,而不依赖垃圾回收机制。
3. 对象池模式
对于需要重用的对象,可以使用对象池模式,而不是依赖finalize()方法进行对象状态重置。
示例:
public class ObjectPoolExample {
private static final List<PoolableObject> pool = new ArrayList<>();
public static PoolableObject acquire() {
if (!pool.isEmpty()) {
return pool.remove(0);
}
return new PoolableObject();
}
public static void release(PoolableObject obj) {
obj.reset();
pool.add(obj);
}
}
class PoolableObject {
// 对象状态
private String state;
public void use(String newState) {
this.state = newState;
}
public void reset() {
this.state = null;
}
}
在这个例子中,我们创建了一个对象池,对象在使用后会被重置并放回池中,而不是依赖垃圾回收机制。
现代Java中的地位
随着Java语言的发展,finalize()方法的重要性已经大大降低。Java 9中引入了java.lang.ref.Cleaner类作为finalize()方法的替代方案,它提供了更可靠和更可控的资源清理机制。Java 14中甚至计划移除finalize()方法,因为它被认为是Java语言中的一个设计缺陷。
现代Java编程中,除非有特殊需求,否则不建议使用finalize()方法。更好的做法是:
- 使用try-with-resources语句管理可关闭资源
- 使用try-finally块确保资源被释放
- 对于对象池等场景,使用显式的对象重置方法
附:java里final、finally、finalize的区别
final :java 关键字。被final修饰的变量不可进行值更改,必须在定义时一并初始化。如final int i=1,则下面对i只能使用,而不能进行更改如i++,更改必定会报错。同理,final修饰方法时,则子类不能对该方法进行重写;被final修饰的类不允许继承。所以,一个类不能不同被abstract和final修饰。(实操验证见下图)

finally:多见与java的try..catch..finally块,只有try语句块正常执行(不做退出线程,死机等情况的考虑),那么无论catch语句块是否执行 ,finally语句块都会执行,且最终整个try..finally代码块返回情况以finally中的return结果为准(见下图)。所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。

finalize:类的finalize()方法,可以告诉垃圾回收器应该执行的操作,该方法从Object类继承而来。在从堆中永久删除对象之前,垃圾回收器调用该对象的finalize()方法。下图是Object类中的finalize 方法。

总结
Java的finalize()方法是一个特殊的方法,它在对象被垃圾回收器回收之前被调用,主要用于执行一些清理工作。虽然它提供了一种在对象销毁前进行资源释放和状态重置的机制,但由于其调用不确定性、性能开销和潜在的并发问题,现代Java编程中已经不推荐使用它。
更好的做法是使用try-with-resources语句、try-finally块和显式的资源管理代码来确保资源被正确释放。对于对象池等场景,应该使用显式的对象重置方法,而不是依赖finalize()方法。随着Java语言的发展,finalize()方法的重要性已经大大降低,未来可能会被完全移除。
到此这篇关于Java中finalize()方法的文章就介绍到这了,更多相关Java中finalize()方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
