什么时候在Java中调用finalize()方法?

我需要知道什么时候在JVM中调用finalize()方法。我创建了一个测试类,当finalize()方法被调用时,通过重写它写入文件。它没有被执行。有人能告诉我为什么它不能执行吗?

523672 次浏览

finalize方法在对象即将被垃圾收集时被调用。这可以在它有资格进行垃圾收集之后的任何时间进行。

注意,完全有可能一个对象从未被垃圾收集(因此finalize从未被调用)。当对象永远不符合gc条件(因为在JVM的整个生命周期内都可以访问它),或者在对象符合条件到JVM停止运行之间没有实际运行垃圾收集时(这种情况经常发生在简单的测试程序中),就会发生这种情况。

有一些方法可以告诉JVM在尚未调用的对象上运行finalize,但使用它们也不是一个好主意(该方法的保证也不是很强)。

如果你依赖finalize来实现应用程序的正确操作,那么你就做错了一些事情。finalize应该用于清理(通常是非java)资源。这就是完全,因为JVM不保证在任何对象上调用finalize

在Java中什么时候调用finalize()方法?

finalize方法将在GC检测到对象不再可达之后调用,并且在它实际回收对象所使用的内存之前调用。

  • 如果一个对象永远不可达,finalize()将永远不会被调用。

  • 如果GC没有运行,那么finalize()可能永远不会被调用。(通常,GC只在JVM认为可能有足够多的垃圾值得执行时运行。)

  • 在GC确定特定对象不可达之前,可能需要一个以上的GC周期。(Java gc通常是“分代”收集器…)

  • 一旦GC检测到一个对象不可达且可终结,它就会被放到终结队列中。终结通常与普通GC异步发生。

(JVM规范实际上允许一个JVM 从不运行终结器…前提是它不回收对象所使用的空间。以这种方式实现的JVM将是残废的/无用的,但这种行为是“允许的”。)

其结果是,依赖最终确定来完成必须在特定时间框架内完成的事情是不明智的。根本不使用它们是“最佳实践”。应该有更好(即更可靠)的方法来做你在finalize()方法中试图做的事情。

终结的唯一合法用途是清理应用程序代码丢失的与对象相关的资源。即使这样,您也应该尝试编写应用程序代码,使其在一开始就不会丢失对象。(例如,使用Java 7+ try-with-resources来确保close()总是被称为…)


我创建了一个测试类,当finalize()方法被重写时写入文件。它没有被执行。有人能告诉我为什么它不能执行吗?

这很难说,但有几种可能性:

  • 对象不会被垃圾收集,因为它仍然是可达的。
  • 对象不会被垃圾收集,因为GC在测试结束之前不会运行。
  • 对象由GC找到,并由GC放置在终结队列中,但是在测试结束之前,终结不会完成。

Java finalize()方法不是析构函数,不应用于处理应用程序所依赖的逻辑。Java规范声明,不能保证在应用程序的生存期内调用finalize方法。

你可能想要的是finally和一个清理方法的组合,如:

MyClass myObj;


try {
myObj = new MyClass();


// ...
} finally {
if (null != myObj) {
myObj.cleanup();
}
}

这将正确处理MyClass()构造函数抛出异常时的情况。

一般来说,最好不要依赖finalize()来做任何清理等。

根据Javadoc(值得一读),它是:

当垃圾回收确定对象不再有引用时,由垃圾回收器在对象上调用。

正如Joachim指出的,如果对象总是可访问的,那么在程序的生命周期中,这可能永远不会发生。

此外,垃圾收集器不保证在任何特定时间运行。一般来说,我想说的是finalize()可能不是最好的方法,除非你有特定的事情需要它。

protected void finalize() throws Throwable {}
  • 每个类继承自finalize()方法 李java . lang . object < / >
  • 当垃圾回收器确定 不再引用该对象 李存在< /强> < / >
  • 对象finalize方法不执行任何操作,但它可以被 任何类李< / >
  • 通常应该被覆盖以清理非java资源,即关闭 李文件< / >
  • 如果覆盖finalize(),则使用a是良好的编程实践 Try-catch-finally语句和to 总是调用super.finalize()。这 是否有安全措施保证你做到 不是无心的错过合上了一个 调用对象所使用的资源 类< / p >

    protected void finalize() throws Throwable {
    try {
    close();        // close open files
    } finally {
    super.finalize();
    }
    }
    
  • any exception thrown by finalize() during garbage collection halts the finalization but is otherwise ignored

  • finalize() is never run more than once on any object

quoted from: http://www.janeg.ca/scjp/gc/finalize.html

You could also check this article:

完成方法不保证。当对象符合GC条件时调用此方法。在许多情况下,对象可能不会被垃圾收集。

Finalize将打印出类创建的计数。

protected void finalize() throws Throwable {
System.out.println("Run F" );
if ( checkedOut)
System.out.println("Error: Checked out");
System.out.println("Class Create Count: " + classCreate);
}

主要

while ( true) {
Book novel=new Book(true);
//System.out.println(novel.checkedOut);
//Runtime.getRuntime().runFinalization();
novel.checkIn();
new Book(true);
//System.runFinalization();
System.gc();

如你所见。下面的输出显示了类计数为36时第一次执行的gc。

C:\javaCode\firstClass>java TerminationCondition
Run F
Error: Checked out
Class Create Count: 36
Run F
Error: Checked out
Class Create Count: 48
Run F
如果一个对象不能从任何活动线程或任何静态引用中访问,那么它就有资格进行垃圾收集或GC。换句话说,如果一个对象的所有引用都为空,那么它就有资格进行垃圾收集。循环依赖关系不作为引用计算,因此如果对象A有对象B的引用,对象B有对象A的引用,并且它们没有任何其他的活动引用,那么对象A和B都将符合垃圾收集的条件。 通常在以下情况下,Java中的对象符合垃圾收集条件:

  1. 该对象的所有引用显式设置为空,例如object = null
  2. 对象在块内创建,一旦控件退出该块,引用就会离开作用域。
  3. 父对象设置为空,如果一个对象持有另一个对象的引用,并且当您将容器对象的引用设置为空时,子对象或包含对象自动符合垃圾收集的条件。
  4. 如果一个对象只有通过WeakHashMap的活动引用,那么它就有资格进行垃圾收集。

查看Effective Java,第二版第27页。 第7项:避免结束符

终结器是不可预测的,通常是危险的,而且通常是不必要的。从不在终结器中做任何时间关键的事情。从来没有 依赖于终结器来更新关键的持久状态

要终止一个资源,请使用try-finally:

// try-finally block guarantees execution of termination method
Foo foo = new Foo(...);
try {
// Do what must be done with foo
...
} finally {
foo.terminate(); // Explicit termination method
}

由于JVM调用finalize()方法存在不确定性(不确定被覆盖的finalize()是否会被执行),为了研究目的,观察finalize()调用时发生的情况的更好方法是通过命令System.gc()强制JVM调用垃圾收集。

具体来说,finalize()在对象不再使用时被调用。但是当我们试图通过创建新对象来调用它时,它的调用是不确定的。因此,为了确定起见,我们创建了一个null对象c,它显然没有未来的用途,因此我们看到对象c的finalize调用。

例子

class Car {


int maxspeed;


Car() {
maxspeed = 70;
}


protected void finalize() {


// Originally finalize method does nothing, but here we override finalize() saying it to print some stmt
// Calling of finalize is uncertain. Difficult to observe so we force JVM to call it by System.gc(); GarbageCollection


System.out.println("Called finalize method in class Car...");
}
}


class Bike {


int maxspeed;


Bike() {
maxspeed = 50;
}


protected void finalize() {
System.out.println("Called finalize method in class Bike...");
}
}


class Example {


public static void main(String args[]) {
Car c = new Car();
c = null;    // if c weren`t null JVM wouldn't be certain it's cleared or not, null means has no future use or no longer in use hence clears it
Bike b = new Bike();
System.gc();    // should clear c, but not b
for (b.maxspeed = 1; b.maxspeed <= 70; b.maxspeed++) {
System.out.print("\t" + b.maxspeed);
if (b.maxspeed > 50) {
System.out.println("Over Speed. Pls slow down.");
}
}
}
}

输出

    Called finalize method in class Car...
1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
20      21      22      23      24      25      26      27      28      29
30      31      32      33      34      35      36      37      38      39
40      41      42      43      44      45      46      47      48      49
50      51Over Speed. Pls slow down.
52Over Speed. Pls slow down.
53Over Speed. Pls slow down.
54Over Speed. Pls slow down.
55Over Speed. Pls slow down.
56Over Speed. Pls slow down.
57Over Speed. Pls slow down.
58Over Speed. Pls slow down.
59Over Speed. Pls slow down.
60Over Speed. Pls slow down.
61Over Speed. Pls slow down.
62Over Speed. Pls slow down.
63Over Speed. Pls slow down.
64Over Speed. Pls slow down.
65Over Speed. Pls slow down.
66Over Speed. Pls slow down.
67Over Speed. Pls slow down.
68Over Speed. Pls slow down.
69Over Speed. Pls slow down.
70Over Speed. Pls slow down.

请注意 -即使在打印到70之后,对象b在程序中没有被使用,也不确定b是否被JVM清除,因为“调用的finalize方法在类Bike…”没有打印。

在最近与终结器方法搏斗之后(为了在测试期间处理连接池),我不得不说终结器缺少很多东西。使用VisualVM来观察以及使用弱引用来跟踪实际的交互,我发现以下事情在Java 8环境中是正确的(Oracle JDK, Ubuntu 15):

  • Finalize不会立即调用,Finalizer (GC部分)难以捉摸地单独拥有引用
  • 默认的垃圾收集器池不可达对象
  • Finalize批量调用,指向一个实现细节,即垃圾收集器在某个阶段释放资源。
  • 调用System.gc()通常不会导致更频繁地完成对象,它只会导致Finalizer更快地意识到不可访问的对象
  • 创建线程转储几乎总是会触发终结器,因为在执行堆转储或其他一些内部机制期间会产生很高的堆开销
  • 终结接缝被内存需求(释放更多的内存)或被标记为终结的对象列表限制在一定的内部限制范围内。因此,如果你有很多对象被最终确定,那么与只有少数对象相比,最终确定阶段将更频繁、更早地被触发
  • 在某些情况下,System.gc()直接触发finalize操作,但前提是引用是本地的短存活对象。这可能与世代有关。

最后认为

最后确定方法是不可靠的,但只能用于一件事。您可以确保在垃圾收集之前关闭或释放对象,从而在正确处理涉及生命结束操作的更复杂生命周期的对象时实现故障安全。这是我能想到的一个值得我们去推翻它的原因。

类,在其中重写finalize方法

public class TestClass {
public TestClass() {
System.out.println("constructor");
}


public void display() {
System.out.println("display");
}
@Override
public void finalize() {
System.out.println("destructor");
}
}

finalize方法被调用的几率

public class TestGarbageCollection {
public static void main(String[] args) {
while (true) {
TestClass s = new TestClass();
s.display();
System.gc();
}
}
}

当内存被转储对象重载时,gc将调用finalize方法

运行并查看控制台,在那里你不会发现finalize方法被频繁调用,当内存超载时,finalize方法将被调用。

Java允许对象实现一个名为finalize()的方法 这可能会被调用。

finalize()方法被调用,如果垃圾回收器尝试

.收集对象

如果垃圾收集器不运行,则不会调用该方法。

如果垃圾收集器收集对象失败并试图运行 同样,该方法在第二次中没有被调用

实际上,您不太可能在实际项目中使用它。

只要记住它可能不会被调用,并且它肯定 不会被叫第二次。finalize()方法可以运行0或1 时间。< / p >

在下面的代码中,finalize()方法在执行 运行它,因为在需要运行 垃圾收集器。< / p >

Source .

试着运行这个程序,以便更好地理解

public class FinalizeTest
{
static {
System.out.println(Runtime.getRuntime().freeMemory());
}


public void run() {
System.out.println("run");
System.out.println(Runtime.getRuntime().freeMemory());
}


protected void finalize() throws Throwable {
System.out.println("finalize");
while(true)
break;
}


public static void main(String[] args) {
for (int i = 0 ; i < 500000 ; i++ ) {
new FinalizeTest().run();
}
}
}
有时,当一个对象被销毁时,它必须做出一个动作。例如,如果一个对象具有非java资源,如文件句柄或字体,您可以在销毁对象之前验证这些资源是否已释放。为了管理这种情况,java提供了一种叫做“终结”的机制。通过终结它,您可以定义当对象即将从垃圾收集器中移除时发生的特定操作。 要向类中添加终结器,只需定义finalize ()方法。Java执行时在将要删除该类的对象时调用此方法。在finalize 方法()中,指定销毁对象之前要执行的操作。 垃圾收集器定期搜索不再引用任何运行状态或间接引用任何其他对象的对象。在资产被释放之前,Java运行时调用对象上的finalize ()方法。finalize ()方法的一般形式如下:

protected void finalize(){
// This is where the finalization code is entered
}
使用受保护的关键字,阻止类外的代码访问finalize ()。 重要的是要理解finalize ()是在垃圾收集之前调用的。例如,当对象离开作用域时,它不会被调用。这意味着你无法知道finalize ()何时或是否将被执行。因此,程序必须提供其他方法来释放系统资源或对象使用的其他资源。

. 0你不应该依赖finalize ()来正常运行程序

finalize()在垃圾收集之前被调用。当对象超出作用域时,不调用它。这意味着你无法知道finalize()何时甚至是否会被执行。

例子:

如果你的程序在垃圾回收发生之前结束,那么finalize()将不会执行。因此,它应该被用作备份过程,以确保其他资源的正确处理,或用于特殊用途的应用程序,而不是作为您的程序在其正常运行中使用的手段。

正如https://wiki.sei.cmu.edu/confluence/display/java/MET12-J.+Do+not+use+finalizers所指出的,

没有必须执行终结器的固定时间,因为执行时间取决于Java虚拟机(JVM)。唯一的保证是,任何执行的终结器方法都将在关联对象变得不可达之后(在垃圾收集的第一个周期中检测到),以及在垃圾收集器回收关联对象的存储之前(在垃圾收集器的第二个周期中)执行。在对象变得不可访问之后,对象终结器的执行可能会延迟一段任意长的时间。因此,在对象的finalize()方法中调用诸如关闭文件句柄之类的时间关键型功能是有问题的。

JDK 18的最新消息

根据openjdk 18上发布的中421finalize()方法的终结和功能将被标记为deprecated(forRemoval=true),这意味着永久删除将在jdk 18之后的某个后续版本中进行。

从jdk 18开始,一个新的命令行选项--finalization=disabled禁用了所有地方的终结机制,甚至对于jdk本身内部的声明也是如此。

这也与这里的这个问题有关,因为它被计划删除的原因是它包含的一些主要缺陷。其中一个缺陷是,从一个对象变得不可访问到调用它的终结器之间可能会经过很长一段时间。GC也不能保证任何终结器将被调用,这也是事实。