640

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

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

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

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

4

25 回答 25

569

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

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

最近有一个问题引发了对 finalize 的深入讨论,所以如果需要的话应该提供更多的深度......

于 2008-10-05T13:17:15.533 回答
141

查看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在应用程序的生存期内完全调用方法。

于 2011-12-02T03:25:14.493 回答
115

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

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

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

于 2008-10-05T13:20:20.660 回答
31

应避免使用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 版本的等效文档)。

于 2008-10-05T13:50:53.500 回答
24

随着 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子句中显式执行它。

于 2012-07-30T15:30:09.143 回答
15

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

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

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

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

于 2008-10-05T14:03:21.667 回答
12

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

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

于 2008-10-05T13:14:11.967 回答
7

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

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

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

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

于 2008-10-05T15:42:26.927 回答
6

finalize()函数是析构函数。

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

此外,需要不止一次 GC 来释放具有finalize().

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

于 2008-10-05T13:16:46.940 回答
6

我同意大多数答案。

你不应该完全依赖任何一个finalizeShutdownHook

敲定

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

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

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

  4. ExceptionGC 线程会忽略 finalizing 方法抛出的任何内容

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

关闭钩子

public void addShutdownHook(Thread hook)

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

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

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

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

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

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

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

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

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

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

结论适当使用try{} catch{} finally{}块并在块中释放关键资源 finally(}。在finally{}块中释放资源期间,catchExceptionThrowable.

于 2016-05-13T15:50:59.350 回答
4

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

于 2008-10-05T16:04:31.860 回答
3

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

于 2008-10-05T13:51:17.167 回答
3

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

@Cleanup
ResourceClass resource = new ResourceClass();

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

@Cleanup("dispose")
ResourceClass resource = new ResourceClass();
于 2016-12-28T12:58:52.883 回答
2

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

于 2008-10-05T13:20:01.783 回答
2

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

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

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

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

代码可能看起来像这样:

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

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

于 2016-11-03T20:21:24.183 回答
2

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

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

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

你可以打电话System.runFinalization(),但它只会“尽最大努力完成所有未完成的最终确定”——而不是保证。

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

于 2019-07-24T18:16:02.450 回答
1

如果您正在编写 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().

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

于 2009-08-25T14:54:00.307 回答
0

尽管 Java 的 GC 技术已经取得了相当大的进步,但您仍然需要注意您的引用。脑海中浮现出许多看似微不足道的参考模式实际上是老鼠窝的案例。

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

于 2008-10-05T17:39:56.317 回答
0

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

于 2020-05-23T19:48:10.050 回答
0

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

于 2020-09-13T17:22:42.237 回答
0

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

于 2021-09-23T08:05:07.937 回答
0

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

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

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

于 2021-12-17T16:30:12.473 回答
0

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

@javax.enterprise.context.ApplicationScoped
public class Foo {

  @javax.annotation.PreDestroy
  public void cleanup() {
    // do your cleanup    
  }
}
于 2022-01-20T16:26:21.067 回答
-1

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

完成()

一个问题引发了对 finalize 的深入讨论,因此如果需要,您应该更深入地讨论......

于 2018-07-05T09:16:25.650 回答
-3

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

例子:

public myDestructor() {

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

}

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

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

于 2013-05-17T16:14:28.310 回答