【问题标题】:Is there a destructor for Java?Java有析构函数吗?
【发布时间】:2010-09-15 09:02:43
【问题描述】:

Java 有析构函数吗?我似乎无法找到任何有关此的文档。如果没有,我怎样才能达到同样的效果?

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

我通常是一名 C/C++ 程序员,我认为这很容易实现。 (因此我计划最后实现它。)我构建了我的程序,使所有“可重置”对象都在同一个类中,这样我就可以在按下重置按钮时销毁所有“活动”对象。

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

【问题讨论】:

  • 如果你持有对你不需要的对象的引用,那么只有内存泄漏。即您的程序中有错误。 GC 将根据需要运行(有时会更快)
  • 如果你通过对象快速处理数据,虚拟机不会很快运行 GC。认为 GC 总能跟上进度或做出正确决定的想法是错误的。
  • @Kieveli JVM 不会在报错之前运行 GC 吗?
  • 是的,如果有 Java 的析构函数可以一次性销毁它就好了。
  • @WVrock - 有趣的问题。答案是“不”(至少对于某些类型的“通过对象快速处理数据”),但原因很微妙。当您将 97% 的时间花在垃圾收集上,而只有 3% 的时间花在实际程序逻辑上时,就会发生实际错误,因为大多数引用仍然有指向它们的指针。如果“快速处理”使用少量指针不会有问题。

标签: java garbage-collection destructor finalize


【解决方案1】:

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

有一个名为finalize 的继承方法,但这完全由垃圾收集器决定调用。所以对于需要明确整理的类,约定是定义一个 close 方法并使用 finalize 仅用于完整性检查(即,如果 close 没有被调用,现在就做并记录错误)。

最近有a question that spawned in-depth discussion of finalize,所以如果需要的话应该提供更多的深度......

【讨论】:

  • 这里的“close()”是指java.lang.Autocloseable中的方法吗?
  • 不,AutoCloseable 是在 Java 7 中引入的,但“close()”约定的使用时间要长得多。
  • 为什么你无法预测一个对象何时(或什至)会被销毁。还有什么近似的预测方法?
  • @dctremblay 对象销毁由垃圾收集器完成,垃圾收集器可能永远不会在应用程序的生命周期内运行。
  • 请注意,Java 9 中的 finalize 方法 has been deprecated
【解决方案2】:

查看try-with-resources 声明。例如:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  System.out.println(br.readLine());
} catch (Exception e) {
  ...
} finally {
  ...
}

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

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

【讨论】:

  • 我很惊讶这票数这么少。这是实际的答案。
  • 我不同意这是实际答案。如果一个实例有一个资源它在更长的时间内处理多个方法调用,那么 try-with-resources 将无济于事。除非可以按照所述方法被调用的速率关闭和重新打开所述资源 - 这不是一般事实。
  • 确实,这不是真正的答案。除非对象的构造和使用完全被try封装,并且finally被用来强制调用obj.finalize(),否则不可能使用这种结构来管理对象的销毁。甚至这种设置也不能解决 OP 带来的问题:由“重置”按钮触发的对象销毁中间程序。
  • 其他用户已在您的应用程序入口点显示此操作。全局定义变量。在入口函数中初始化它,用try。在 finally 上取消初始化(当您的应用程序关闭时)。完全有可能。
  • @nurettin Java 7 在被问到这个问题时才发布了 3 个月,如果这有助于更理解它。
【解决方案3】:

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

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

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

【讨论】:

  • 感谢您提及本机资源 - 这是“类似析构函数”方法有用的一个领域。
  • 是的,我现在正面临同样的问题,需要释放通过对 C++ 的本机调用分配的资源/句柄。
  • @ddimitrov,理论上java可以实现显式释放吗?或者这是一个逻辑矛盾?
  • @mils 天真地实现显式释放会破坏 Java 的假设,即任何引用都指向活动对象。您可以遍历所有指针并将别名归零,但这比 GC 更昂贵。或者你可以尝试使用一些线性类型系统(参见 Rust 中的“所有权”),但这是一个重大的语言变化。还有其他选项(请参阅 JavaRT 范围内存等),但通常显式释放并不适合 Java 语言。
【解决方案4】:

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

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

Resource r = new Resource();
try {
    //work
} finally {
    r.dispose();
}

尝试使用已处置的对象应引发运行时异常(请参阅IllegalStateException)。


编辑:

我在想,如果我所做的只是 取消引用数据并等待 垃圾收集器来收集它们, 如果我的不会有内存泄漏 用户重复输入数据和 按下重置按钮?

通常,您需要做的就是取消引用对象 - 至少,这是它应该工作的方式。如果您担心垃圾收集,请查看Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning(或您的 JVM 版本的等效文档)。

【讨论】:

  • 这不是取消引用的意思。它不是“将对象的最后一个引用设置为 null”,而是从引用中获取(读取)值,以便您可以将其用于后续操作。
  • try..finally 仍然是一种有效且推荐的方法吗?假设我之前在 finalize() 中调用了本机方法,我可以将调用移至 finally 子句吗? class Resource { finalize() { destroy(); } protected native void destroy(); } class Alt_Resource { try (Resource r = new Resource()) { // use r } finalize { r.destroy(); }
  • r 不会被限制在 finally 块中。因此,此时您不能调用destroy。现在,如果您更正范围以在 try 块之前创建对象,那么您最终会遇到丑陋的“在 try-with-resources 之前”的情况。
【解决方案5】:

随着 Java 1.7 的发布,您现在可以选择使用 try-with-resources 块。例如,

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

如果你执行这个类,c.close() 将在try 块离开时执行,在catchfinally 块执行之前。与finalize() 方法不同,close() 保证会被执行。但是,不需要在finally 子句中显式执行。

【讨论】:

  • 如果我们不使用 try-with-resources 块会怎样?我认为我们可以在 finalize() 中调用 close 以确保已调用 close。
  • @shintoZ 正如我在上面的答案中所读到的,不能保证 finalize() 执行
【解决方案6】:

我完全同意其他答案,说不依赖finalize的执行。

除了 try-catch-finally 块之外,您还可以使用 Runtime#addShutdownHook(在 Java 1.3 中引入)在您的程序中执行最终清理。

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

这样做的好处是解构行为与程序的其余部分松散耦合

【讨论】:

  • addShutdownHook 显然是在 Java 1.3 中引入的。无论如何,它在 1.5 中可供我使用。 :) 看到这个:stackoverflow.com/questions/727151/…
  • 仅供参考,根据我的经验,如果您在 Eclipse 中使用红色的“终止”按钮,则不会调用关闭挂钩 - 整个 JVM 会立即被销毁,关闭挂钩不会被优雅地调用。这意味着如果您使用 eclipse 进行开发,您可能会在开发和生产过程中看到不同的行为
【解决方案7】:

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

但是,不能保证何时(以及是否)调用它。
见:java.lang.Runtime#runFinalizersOnExit(boolean)

【讨论】:

  • 在我的书中,可能会或可能不会被调用的方法本质上是无用的。最好不要用一种无用的特殊方法污染语言,这种方法充其量只会给人一种虚假的安全感。我永远无法理解为什么 Java 语言的开发人员认为 finalize 是个好主意。
  • @antred The developers of the Java language agree。我想,当时,对于其中一些人来说,这是他们第一次设计带有垃圾收集的编程语言和运行时环境。更难理解的是,为什么其他托管语言 copied that concept 在已经被理解为这个概念是一个坏主意的时候。
【解决方案8】:

首先,请注意,由于 Java 是垃圾收集的,因此很少需要对对象销毁做任何事情。首先是因为您通常没有任何可释放的托管资源,其次是因为您无法预测何时或是否会发生,因此对于“一旦没有人再使用我的对象”就需要发生的事情是不合适的”。

可以在使用 java.lang.ref.PhantomReference 销毁对象后通知您(实际上,说它已被销毁可能有点不准确,但如果对它的幻像引用排队,则它不再可恢复,这通常相当于同一件事)。一个常见的用法是:

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

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

【讨论】:

  • 为什么使用 PhantomReference 而不是 WeakReference?
  • @uckelman:如果你想要的只是通知,那么 PhantomReference 就可以完成这项工作,这几乎就是它的设计目的。此处不需要 WeakReference 的附加语义,并且在通知您的 ReferenceQueue 时,您无法再通过 WeakReference 恢复对象,因此使用它的唯一原因是不必记住 PhantomReference 存在。 WeakReference 所做的任何额外工作可能都可以忽略不计,但为什么还要费心呢?
  • 感谢您对 PhantomReference 的提示。它并不完美,但总比没有好。
  • @SteveJessop 与幻像引用相比,您认为什么“额外工作”具有弱引用?
【解决方案9】:

finalize() 函数是析构函数。

但是,它不应该被正常使用,因为它是在GC之后调用的,你无法判断什么时候会发生(如果有的话)。

此外,释放具有finalize() 的对象需要不止一次 GC。

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

【讨论】:

    【解决方案10】:

    我同意大部分答案。

    您不应完全依赖finalizeShutdownHook

    finalize

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

    2. finalize() 只被 GC 线程调用一次。如果一个对象从 finalizing 方法中恢复过来,那么finalize 将不会被再次调用。

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

    4. 任何由 finalizing 方法抛出的Exception 都会被 GC 线程忽略

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

    shutdownHooks

    public void addShutdownHook(Thread hook)
    

    注册一个新的虚拟机关闭钩子。

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

    1. 程序正常退出,当最后一个非守护线程退出,或者调用了exit(相当于System.exit)方法时,或者

    2. 虚拟机响应用户中断(例如键入 ^C)或系统范围的事件(例如用户注销或系统关闭)而终止。

    3. 关闭挂钩只是一个已初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩并让它们同时运行。当所有钩子都完成后,如果 finalization-on-exit 已启用,它将运行所有未调用的终结器。

    4. 最后,虚拟机将停止。请注意,在关闭序列期间,守护线程将继续运行,如果通过调用 exit 方法启动关闭,非守护线程也将继续运行。

    5. 关闭挂钩也应该快速完成它们的工作。当程序调用 exit 时,期望虚拟机会立即关闭并退出。

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

    在极少数情况下,虚拟机可能会中止,即在没有完全关闭的情况下停止运行

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

    结论适当使用try{} catch{} finally{} 块并释放finally(} 块中的关键资源。在finally{}块释放资源时,捕获ExceptionThrowable

    【讨论】:

      【解决方案11】:

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

      【讨论】:

        【解决方案12】:

        也许您可以使用 try ... finally 块来最终确定您正在使用该对象的控制流中的对象。当然它不会自动发生,但 C++ 中的破坏也不会。您经常会在 finally 块中看到资源关闭。

        【讨论】:

        • 当相关资源只有一个所有者并且从未被其他代码“窃取”过对它的引用时,这是正确的答案。
        【解决方案13】:

        Lombok 中有一个@Cleanup 注释,主要类似于 C++ 析构函数:

        @Cleanup
        ResourceClass resource = new ResourceClass();
        

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

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

        【讨论】:

        • 我看到的好处是嵌套更少(如果您有很多需要“销毁”的对象,这可能很重要)。
        • 一个 try-with-resource 块可以同时拥有多个资源
        • 但是它们之间不能有指令。
        • 公平。我想那么建议是更喜欢 try-with-resource,即使是多个资源,除非它们之间需要有指令迫使您创建新的 try-with-resource 块(增加嵌套),然后使用 @Cleanup
        【解决方案14】:

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

        【讨论】:

          【解决方案15】:

          想想最初的问题......我认为我们可以从所有其他学到的答案中得出结论,也可以从 Bloch 的基本 Effective Java 第 7 项“避免终结器”中得出结论,寻求解决一个合法问题以不适合 Java 语言的方式...:

          ... 不是一个非常明显的解决方案来做 OP 实际想要的是将所有需要重置的对象保留在一种“游戏笔”中,所有其他不可重置的对象都只有引用通过某种访问器对象...

          然后,当您需要“重置”时,您断开现有的围栏并制作一个新围栏:围栏中的所有对象网络都被抛到一边,永远不会返回,总有一天会被 GC 收集。

          如果这些对象中的任何一个是Closeable(或不是,但有close 方法),您可以在创建(并可能打开)时将它们放在游戏围栏中的Bag 中,最后一幕在切断游戏围栏之前访问者将通过所有Closeables 关闭它们...?

          代码可能看起来像这样:

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

          closeCloseables 可能是一种阻塞方法,可能涉及锁存器(例如CountdownLatch),以处理(并在适当的时候等待)任何Runnables/Callables 特定于@987654331 的线程中的任何Callables @ 将酌情结束,特别是在 JavaFX 线程中。

          【讨论】:

            【解决方案16】:

            这里有很多很好的答案,但还有一些关于为什么应该避免使用 finalize() 的附加信息。

            如果JVM由于System.exit()Runtime.getRuntime().exit()而退出,默认情况下不会运行终结器。来自Javadoc for Runtime.exit()

            虚拟机的关闭顺序包括两个阶段。在第一阶段,所有注册的关闭钩子(如果有)都以某种未指定的顺序启动,并允许同时运行直到它们完成。在第二阶段,如果已启用 finalization-on-exit,则运行所有未调用的终结器。一旦完成,虚拟机就会停止。

            您可以致电System.runFinalization(),但它只会“尽最大努力完成所有未完成的最终确定”——并非保证。

            有一个System.runFinalizersOnExit() 方法,但不要使用它——它不安全,很久以前就被弃用了。

            【讨论】:

              【解决方案17】:

              如果您正在编写 Java Applet,您可以覆盖 Applet 的“destroy()”方法。这是……

               * 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().
              

              显然不是想要的,但可能是其他人正在寻找的。​​p>

              【讨论】:

                【解决方案18】:

                尽管 Java 的 GC 技术取得了相当大的进步,但您仍然需要注意您的引用。许多看似微不足道的参考模式实际上是引擎盖下的老鼠巢。

                从您的帖子看来,您似乎不是为了对象重用而尝试实现重置方法(是吗?)。您的对象是否拥有任何其他类型的需要清理的资源(即必须关闭的流、必须归还的任何池化或借用的对象)?如果您唯一担心的是内存释放,那么我会重新考虑我的对象结构并尝试验证我的对象是自包含结构,这些结构将在 GC 时被清理。

                【讨论】:

                  【解决方案19】:

                  没有Java没有任何析构函数。Java中它背后的主要原因是垃圾收集器总是在后台被动工作,所有对象都在堆内存中,这就是GC工作的地方。在c++ 那里我们必须显式调用删除函数,因为没有像垃圾收集器这样的东西。

                  【讨论】:

                    【解决方案20】:

                    在 Java 中,垃圾收集器会自动删除未使用的对象以释放内存。因此,Java 没有可用的析构函数是明智的。

                    【讨论】:

                      【解决方案21】:

                      当涉及到 android 编程时,尝试调用 onDestroy() 方法。这是在 Activity/Service 类被终止之前执行的最后一个方法。

                      【讨论】:

                        【解决方案22】:

                        缺少我刚刚扫描的所有答案是终结器的更安全替代品。关于使用 try-with-resources 和避免终结器的所有其他答案都是正确的,因为它们不可靠并且现在已被弃用......

                        但是他们没有提到清洁工。 Java 9 中添加了清理器,以比终结器更好的方式显式处理清理工作。

                        https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html

                        【讨论】:

                          【解决方案23】:

                          如果您有机会使用 上下文和依赖注入 (CDI) 框架,例如 Weld,您可以使用 Java 注释 @Predestroy 进行清理工作等等

                          @javax.enterprise.context.ApplicationScoped
                          public class Foo {
                          
                            @javax.annotation.PreDestroy
                            public void cleanup() {
                              // do your cleanup    
                            }
                          }
                          

                          【讨论】:

                            【解决方案24】:

                            Java 中没有完全的析构函数类,java 中的类被垃圾收集器自动销毁。但是您可以使用下面的方法来做到这一点,但这并不完全相同:

                            finalize()

                            a question that spawned in-depth discussion of finalize ,所以如果需要你应该获得更多的深度......

                            【讨论】:

                              【解决方案25】:

                              我过去主要处理 C++,这也是我寻找析构函数的原因。我现在经常使用 JAVA。我做了什么,它可能不是对每个人来说都是最好的情况,但我通过一个函数将所有值重置为 0 或默认值来实现我自己的析构函数。

                              例子:

                              public myDestructor() {
                              
                              variableA = 0; //INT
                              variableB = 0.0; //DOUBLE & FLOAT
                              variableC = "NO NAME ENTERED"; //TEXT & STRING
                              variableD = false; //BOOL
                              
                              }
                              

                              理想情况下,这并不适用于所有情况,但是在存在全局变量的情况下,只要您没有大量变量,它就会起作用。

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

                              【讨论】:

                              • 尝试更多地使用不可变对象,在你“明白”之后一切都会变得更有意义:)
                              • 这并没有什么大错,因为它毫无意义——也就是说,什么也做不了。如果您的程序需要重置原始类型才能正常工作,那么您的类实例的范围不正确,这意味着您可能正在将现有对象的属性重新分配给新对象的属性,而没有创建新的 Object()。跨度>
                              • 除非有大量需要重置变量。我选择了析构函数这个名字,因为它适合我正在做的事情。它确实取得了一些成就,只是没有你能理解
                              猜你喜欢
                              • 2017-11-28
                              • 1970-01-01
                              • 2020-04-06
                              • 2016-02-20
                              • 1970-01-01
                              • 1970-01-01
                              • 2013-02-01
                              • 2015-09-10
                              相关资源
                              最近更新 更多