关于垃圾收集:是否有Java的析构函数?

Is there a destructor for Java?

是否有Java的析构函数?我似乎找不到任何有关这方面的文件。如果没有,我怎样才能达到同样的效果?

为了使我的问题更具体,我正在编写一个处理数据的应用程序,规范说应该有一个"重置"按钮,将应用程序恢复到其最初的刚启动状态。但是,除非关闭应用程序或按下重置按钮,否则所有数据都必须是"实时"的。

通常是C/C++程序员,我认为这将是微不足道的实现。(因此我计划最后实现它。)我构建了我的程序,使所有"可重置"对象都在同一类中,这样当按下重置按钮时,我就可以销毁所有"活动"对象。

我在想,如果我所做的只是取消引用数据并等待垃圾收集器收集它们,如果我的用户反复输入数据并按下重置按钮,是否会出现内存泄漏?我也在想,既然Java作为一种语言已经相当成熟了,应该有办法阻止这种情况发生或优雅地解决这个问题。


因为Java是垃圾收集语言,所以无法预测何时(或甚至)对象将被销毁。因此,没有直接等效的析构函数。

有一个名为finalize的继承方法,但这完全由垃圾收集器决定调用。因此,对于需要显式整理的类,约定是定义一个close方法,并仅将finalize用于健全性检查(即,如果尚未调用close,则立即执行该操作并记录错误)。

最近有一个问题引发了对"最后定稿"的深入讨论,因此如果需要,应该提供更深入的讨论…


如果使用Java 7,请查看"尝试与资源"语句。例如:

1
2
3
4
5
6
7
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  System.out.println(br.readLine());
} catch (Exception e) {
  ...
} finally {
  ...
}

这里不再需要的资源在BufferedReader.close()方法中释放。您可以创建自己的实现AutoCloseable的类,并以类似的方式使用它。

在代码结构方面,此语句比finalize更为有限,但同时它使代码更易于理解和维护。此外,不能保证在应用程序的生命周期内调用finalize方法。


不,这里没有毁灭者。原因是所有Java对象都是堆分配和垃圾收集。没有显式的解除分配(即C++的删除运算符),就没有明智的方法来实现真正的析构函数。

Java确实支持终结器,但是它们只被用作对持有本地资源句柄的对象的保护,例如套接字、文件句柄、窗口句柄等。当垃圾收集器收集没有终结器的对象时,它只是将内存区域标记为空闲的,就是这样。当对象有一个终结器时,它首先被复制到一个临时位置(请记住,我们在这里进行垃圾收集),然后它排队进入一个等待完成的队列,然后终结器线程以非常低的优先级轮询队列并运行终结器。

当应用程序退出时,JVM会停止,而不等待待处理对象完成,因此实际上无法保证终结器将一直运行。


应避免使用finalize()方法。它们不是一种可靠的资源清理机制,滥用它们可能导致垃圾收集器出现问题。

如果需要在对象中进行释放调用,例如释放资源,请使用显式方法调用。这种约定可以在现有的API(例如closeable、graphics.dispose()、widget.dispose())中看到,通常通过try/finally调用。

1
2
3
4
5
6
Resource r = new Resource();
try {
    //work
} finally {
    r.dispose();
}

尝试使用释放的对象应引发运行时异常(请参阅IllegalstateException)。

编辑:

I was thinking, if all I did was just
to dereference the data and wait for
the garbage collector to collect them,
wouldn't there be a memory leak if my
user repeatedly entered data and
pressed the reset button?

通常,您需要做的就是取消对对象的引用——至少,这是它应该工作的方式。如果您担心垃圾回收,请查看Java SE 6热点[TM]虚拟机垃圾收集调优(或JVM版本的等效文档)。


随着Java 1.7的发布,您现在有了使用EDCOX1×4块的额外选项。例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Closeable implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("closing...");
    }
    public static void main(String[] args) {
        try (Closeable c = new Closeable()) {
            System.out.println("trying...");
            throw new Exception("throwing...");
        }
        catch (Exception e) {
            System.out.println("catching...");
        }
        finally {
            System.out.println("finalizing...");
        }
    }
}

如果您执行这个类,那么在离开try块时,以及在执行catchfinally块之前,将执行c.close()。与finalize()方法不同,close()保证被执行。但是,不需要在finally条款中明确执行。


我完全同意其他的答案,说不要依靠执行定案。

除了尝试catch catch块之外,您还可以使用运行时γAdAdSuthDebug钩子(在Java 1.6中引入)来执行程序中的最终清理。

这与析构函数不同,但可以实现一个关闭钩子,在该钩子上注册侦听器对象,可以调用清理方法(关闭持久数据库连接、删除文件锁等),这通常在析构函数中完成。同样-这不是对构造函数的替换,但在某些情况下,您可以使用它来实现所需的功能。

这样做的好处是解构行为与程序的其他部分不耦合。


不,java.lang.Object#finalize是你能得到的最接近的。

但是,当(和如果)它被调用时,不能保证。见:java.lang.Runtime#runFinalizersOnExit(boolean)


首先,请注意,由于Java是垃圾收集的,所以很少需要对对象破坏做任何事情。首先,因为你通常没有任何可管理的资源可供释放,其次,因为你无法预测何时或是否会发生这种情况,所以"一旦没有人再使用我的对象"就不适合你需要发生的事情。

在使用java.lang.ref.phantom reference销毁对象后,您可以收到通知(实际上,说它已被销毁可能有点不准确,但如果对它的幻象引用排队,则它将不再可恢复,这通常等于相同的事情)。常用的用法是:

  • 将类中需要销毁的资源分离为另一个助手对象(注意,如果您所做的只是关闭一个连接(这是常见的情况),则不需要编写新的类:在这种情况下,要关闭的连接将是"助手对象"。
  • 当您创建主对象时,也创建一个对它的幻象引用。或者让它引用新的助手对象,或者设置一个从phantomreference对象到其相应助手对象的映射。
  • 在收集主对象之后,将对phantomreference进行排队(或者更确切地说,它可能是排队的——就像终结器一样,无法保证它会排队,例如,如果VM退出,它将不会等待)。确保您正在处理它的队列(在特殊线程中或不时地)。由于对helper对象的硬引用,尚未收集helper对象。所以,在helper对象上做任何您喜欢的清理,然后丢弃phantomreference,最终也会收集helper。

还有finalize(),它看起来像一个析构函数,但行为却不像。这通常不是一个好的选择。


如果这偏离了主题,我很抱歉,但是java.util.timer(se6)文档说:

"在对计时器对象的最后一个实时引用消失并且所有未完成的任务都已完成执行之后,计时器的任务执行线程将优雅地终止(并成为垃圾收集的主题)。然而,这可能需要任意长的时间才能发生。默认情况下,任务执行线程不作为守护进程线程运行,因此它能够防止应用程序终止。如果调用方希望快速终止计时器的任务执行线程,则调用方应调用计时器的取消方法…"

当拥有计时器的类丢失其最后一个引用时(或之前的immediatelesky),我想调用cancel。在这里,一个可靠的析构函数可以为我做到这一点。上面的评论表明,最终是一个糟糕的选择,但有一个优雅的解决方案吗?"能够阻止应用程序终止…"的业务并不吸引人。


finalize()函数是析构函数。

但是,通常不应该使用它,因为它是在GC之后调用的,并且您不能确定何时会发生这种情况(如果有)。

此外,需要多个GC来释放具有finalize()的对象。

您应该尝试使用try{...} finally{...}语句清理代码中的逻辑位置!


如果这只是你担心的记忆,不要。只要相信GC,它就做得很好。事实上,我看到它的效率非常高,以至于在某些情况下,创建一堆小对象比使用大型数组更能提高性能。


我同意大多数答案。

你不应该完全依赖于finalizeShutdownHook

定稿

  • JVM不保证何时调用此finalize()方法。

  • 如果对象从finaling方法中恢复自身,则GC线程只调用一次finalize(),而不会再次调用finalite。

  • 在您的应用程序中,您可能有一些活动对象,在这些对象上永远不会调用垃圾收集。

  • 终止方法引发的任何异常都被GC线程忽略

  • System.runFinalization(true)Runtime.getRuntime().runFinalization(true)方法增加了调用finalize()方法的概率,但现在这两种方法已被弃用。由于缺少线程安全和可能的死锁创建,这些方法非常危险。

  • 关闭钩

    1
    public void addShutdownHook(Thread hook)

    Registers a new virtual-machine shutdown hook.

    Java虚拟机响应两种事件关闭:

  • 当最后一个非守护进程线程退出或调用exit(相当于system.exit)方法时,程序通常退出,或者
  • 虚拟机因响应用户中断(如键入^C)或系统范围的事件(如用户注销或系统关闭)而终止。
  • 关闭挂钩只是一个初始化但未启动的线程。当虚拟机开始其关闭顺序时,它将以某种未指定的顺序启动所有注册的关闭挂接,并让它们同时运行。当所有钩子完成后,如果启用了退出时的终结,那么它将运行所有未激活的终结器。
  • 最后,虚拟机将停止。请注意,在关闭过程中,守护进程线程将继续运行,如果关闭是通过调用exit方法启动的,则非守护进程线程也将继续运行。
  • 关闭挂钩也应快速完成其工作。当程序调用exit时,期望虚拟机会立即关闭并退出。

    但即使是Oracle文档也引用了这一点

  • In rare circumstances the virtual machine may abort, that is, stop running without shutting down cleanly

    当虚拟机在外部终止时会发生这种情况,例如在Unix上使用SIGKILL信号或在Microsoft Windows上使用TerminateProcess调用。如果本机方法出错,例如损坏内部数据结构或试图访问不存在的内存,虚拟机也可能中止。如果虚拟机中止,则无法保证是否会运行任何关闭挂钩。

    结论:合理使用try{} catch{} finally{}区块,释放finally(}区块关键资源。在finally{}区块资源释放过程中,抓捕ExceptionThrowable


    也许你可以试试……最后一个块,用于完成正在使用对象的控制流中的对象。当然,它不是自动发生的,但是C++中也没有破坏。在finally块中经常会看到资源关闭。


    如果你正在编写一个Java小程序,你可以重写applet"销毁()"方法。它是。。。

    1
    2
    3
    4
     * Called by the browser or applet viewer to inform
     * this applet that it is being reclaimed and that it should destroy
     * any resources that it has allocated. The stop() method
     * will always be called before destroy().

    显然不是你想要的,而是其他人想要的。


    在Java中最接近于析构函数的是FialIZE()方法。与传统的析构函数不同的是,您不能确定何时调用它,因为这是垃圾收集器的责任。我强烈建议在使用它之前仔细阅读一下,因为文件句柄等典型的RAIA模式在finalize()中不能可靠地工作。


    在龙目岛中有一个@清除注释,大多数类似于C++析构函数:

    1
    2
    @Cleanup
    ResourceClass resource = new ResourceClass();

    在处理它时(在编译时),lombok插入适当的try-finally块,以便在执行离开变量范围时调用resource.close()。您还可以显式指定另一种释放资源的方法,例如resource.dispose()

    1
    2
    @Cleanup("dispose")
    ResourceClass resource = new ResourceClass();


    Java中没有确切的析构函数类,垃圾回收器自动在Java中销毁类。但你可以用下面的一个来做,但这并不完全相同:

    终结()

    有一个问题引发了对Finalize的深入讨论,因此如果需要,您应该获得更深入的讨论…


    只想着最初的问题…我认为我们可以从所有其他学习的答案中得出结论,也可以从布洛赫的基本有效Java,项目7,"避免终结器",寻求一种对Java语言不合适的方式来解决一个合法的问题…

    …一个不太明显的解决方案就是把所有需要重置的对象保存在一种"playpen"中,所有其他不可重置的对象都只通过某种访问器对象引用这些对象…

    然后当您需要"重置"时,您断开现有的PlayPen并创建一个新的PlayPen:PlayPen中的所有对象网络都被丢弃,永远不会返回,总有一天会被GC收集。

    如果这些物体中有任何一个是Closeable(或者不是,但是有close方法),你可以把它们放在游戏围栏中创建(并且可能打开)的Bag中,并且在关闭游戏围栏之前,附加器的最后一个动作是通过所有Closeables关闭它们……?

    代码可能如下所示:

    1
    2
    accessor.getPlaypen().closeCloseables();
    accessor.setPlaypen( new Playpen() );

    closeCloseables可能是一种阻塞方法,可能涉及一个锁存器(例如CountdownLatch)来处理(并在适当情况下等待)Runnables/Callables在任何特定于Playpen的线程中的任何Runnables/Callables将视情况结束,特别是在javafx线程中。


    虽然Java的GC技术已经有了相当大的进步,但是你仍然需要注意你的引用。许多看似微不足道的参考模式,实际上是老鼠巢下的遮光罩出现在脑海中。

    从你的帖子中,这听起来不像是你试图实现一个重置方法来实现对象重用(真的?)。您的对象是否包含需要清理的任何其他类型的资源(即必须关闭的流、必须返回的任何集合或借用对象)?如果您唯一担心的是内存释放,那么我将重新考虑我的对象结构,并尝试验证我的对象是在GC时将被清除的自包含结构。


    我过去主要处理C++,这也是导致我去搜索析构函数的原因。我现在正在大量使用Java。我所做的,可能对每个人来说都不是最好的情况,但是我实现了我自己的析构函数,通过函数将所有值重置为0或那里的默认值。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    public myDestructor() {

    variableA = 0; //INT
    variableB = 0.0; //DOUBLE & FLOAT
    variableC ="NO NAME ENTERED"; //TEXT & STRING
    variableD = false; //BOOL

    }

    理想情况下,这并不适用于所有情况,但是如果存在全局变量,只要没有大量的全局变量,它就可以工作。

    我知道我不是最好的Java程序员,但它似乎对我有用。