是否有Java的析构函数?

时间:2008-10-05 13:12:25

标签: java garbage-collection destructor

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

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

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

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

23 个答案:

答案 0 :(得分:490)

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

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

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

答案 1 :(得分:114)

如果您使用Java 7,请查看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方法。

答案 2 :(得分:107)

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

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

当应用程序退出时,JVM会在不等待待定对象完成的情况下停止,因此几乎不能保证终结器能够运行。

答案 3 :(得分:25)

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

答案 4 :(得分:22)

发布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()块被保留时,以及执行trycatch块之前将执行finally。与finalize()方法的情况不同,close()保证会被执行。但是,不需要在finally子句中明确执行它。

答案 5 :(得分:13)

我完全同意其他答案,并表示不要依赖执行finalize。

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

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

这样做的好处是可以从程序的其他部分解析行为松散耦合

答案 6 :(得分:9)

不,java.lang.Object#finalize是您最接近的。

但是,如果(如果)调用它,则无法保证 请参阅:java.lang.Runtime#runFinalizersOnExit(boolean)

答案 7 :(得分:6)

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

使用java.lang.ref.PhantomReference销毁一个对象后可以得到通知(实际上,说它已被销毁可能稍微不准确,但是如果对它的幻像引用排队那么它就不再可恢复了,通常相同的事情)。一个常见的用途是:

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

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

答案 8 :(得分:6)

如果这偏离主题,我很抱歉,但java.util.Timer(SE6)文档说:

“在对Timer对象的最后一次实时引用消失并且所有未完成的任务都已完成执行之后,定时器的任务执行线程正常终止(并且变为垃圾回收)。但是,这可能会花费任意长的时间。默认情况下,任务执行线程不作为守护程序线程运行,因此它能够阻止应用程序终止。如果调用者想要快速终止计时器的任务执行线程,调用者应该调用计时器的取消方法......“

我想在拥有Timer失去最后一个引用(或之前的immeditalesky)的类上取消取消。在这里,一个可靠的析构函数可以为我做到这一点。上面的评论表明,最终是一个糟糕的选择,但有一个优雅的解决方案吗? “......能够阻止申请终止......”的业务并不吸引人。

答案 9 :(得分:4)

finalize()函数是析构函数。

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

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

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

答案 10 :(得分:4)

我同意大多数答案。

您不应完全依赖finalizeShutdownHook

finalize

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

  2. finalize()仅在GC线程中被调用一次,如果object从finalize方法中恢复,则不会再次调用finalize。

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

  4. GC线程忽略了终结方法引发的任何异常

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

  6. shutdownHooks

    public void addShutdownHook(Thread hook)
    
      

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

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

    1. 当最后一个非守护程序线程退出或调用退出(等效,System.exit)方法时,程序正常退出,或者
    2. 虚拟机将终止以响应用户中断,例如键入^ C或系统范围的事件,例如用户注销或系统关闭。
    3. 关闭钩子只是一个初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩,并让它们同时运行。当所有挂钩都完成后,如果启用了finalization-on-exit,它将运行所有未读取的终结器。
    4. 最后,虚拟机将停止运行。请注意,守护程序线程将在关闭序列期间继续运行,如果通过调用exit方法启动关闭,则守护程序线程将继续运行。
    5. 关机挂钩也应该快速完成工作。当程序调用exit时,期望虚拟机将立即关闭并退出。

      但即便是Oracle文档也引用了

    6.   

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

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

      结论 适当地使用try{} catch{} finally{}块并在finally(}块中释放关键资源。在finally{}块中释放资源期间,请抓住ExceptionThrowable

答案 11 :(得分:4)

如果只是记忆你担心,不要。只要相信GC,它就能做得很好。我实际上看到了一些关于它如此高效的东西,以至于在某些情况下创建大量微小对象比使用大型数组更好。

答案 12 :(得分:3)

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

答案 13 :(得分: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().

显然不是想要什么,但可能是其他人想要的。

答案 14 :(得分:1)

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

答案 15 :(得分:1)

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

@Cleanup
ResourceClass resource = new ResourceClass();

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

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

答案 16 :(得分:0)

虽然Java的GC技术已取得了相当大的进步,但仍需要注意您的参考资料。我想到了许多看似琐碎的参考模式的案例,这些模式实际上是老鼠筑巢。

从你的帖子来看,听起来你并没有尝试为了对象重用而实现重置方法(是吗?)。您的对象是否包含需要清理的任何其他类型的资源(即必须关闭的流,必须返回的任何池或借来的对象)?如果您唯一担心的是内存dealloc,那么我会重新考虑我的对象结构并尝试验证我的对象是自包含的结构,将在GC时间清理。

答案 17 :(得分:0)

只考虑原来的问题......我认为我们可以从所有其他学到的答案中得出结论,也可以从Bloch的基本Effective Java第7项中得出结论,"避免终结者和#34;,以不适合Java语言的方式寻找合法问题的解决方案......:

...并不是一个非常明显的解决方案来做OP真正想要的是保持所有需要在某种"围栏"中重置的对象,其他所有非-resettable对象只能通过某种访问器对象进行引用...

然后当你需要"重置"你断开了现有的围栏并建立了一个新的围栏:围栏中的所有物体网都是漂流,永远不会返回,有一天由GC收集。

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

代码可能看起来像这样:

Closeables

accessor.getPlaypen().closeCloseables(); accessor.setPlaypen( new Playpen() ); 可能是一种阻止方法,可能涉及一个锁存器(例如closeCloseables),以处理(并酌情等待)任何CountdownLatch / Runnables在适当的任何特定于Callables的线程中,特别是在JavaFX线程中。

答案 18 :(得分:0)

Java中没有确切的析构函数类,垃圾回收器会自动在Java中销毁该类。但是您可以使用以下一项来做到这一点,但这并不是完全相同的事情:

finalize()

a question that spawned in-depth discussion of finalize,因此,如果需要,您应该更深入...

答案 19 :(得分:0)

这里有很多不错的答案,但是还有一些其他信息说明为什么您应该避免使用finalize()

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

  

虚拟机的关闭序列由两个阶段组成。在第一阶段,所有已注册的关闭挂接(如果有)以某种未指定的顺序启动,并允许它们并发运行直到它们完成。在第二阶段,如果启用了退出完成,则所有未调用的终结器都将运行。完成此操作后,虚拟机将停止。

您可以致电System.runFinalization(),但这仅是“尽一切努力完成所有出色的定稿工作”,而不是保证。

有一种System.runFinalizersOnExit()方法,但是不要使用-它是不安全的,很久以前就已弃用。

答案 20 :(得分:0)

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

答案 21 :(得分:0)

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

答案 22 :(得分:-1)

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

示例:

public myDestructor() {

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

}

理想情况下,这不适用于所有情况,但只要你没有大量的全局变量就会有效。

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