Java 有析构函数吗?
-
05-07-2019 - |
题
Java 有析构函数吗?我似乎找不到任何关于此的文档。如果没有,怎样才能达到同样的效果?
为了使我的问题更具体,我正在编写一个处理数据的应用程序,并且规范说应该有一个“重置”按钮,使应用程序恢复到其原始的刚刚启动状态。但是,除非应用程序关闭或按下重置按钮,否则所有数据都必须是“实时”的。
作为一名 C/C++ 程序员,我认为这实现起来很简单。(因此我计划最后实现它。)我构建了我的程序,使所有“可重置”对象都位于同一类中,这样当按下重置按钮时我就可以销毁所有“活动”对象。
我在想,如果我所做的只是取消引用数据并等待垃圾收集器来收集它们,如果我的用户重复输入数据并按下重置按钮,是否会出现内存泄漏?我也在想,由于 Java 作为一门语言已经相当成熟,应该有一种方法来防止这种情况发生或优雅地解决这种情况。
解决方案
因为Java是一种垃圾收集语言,所以无法预测何时(或者甚至是)对象将被销毁。因此,没有直接等价的析构函数。
有一个名为 finalize
的继承方法,但完全由垃圾收集器自行调用。因此,对于需要明确整理的类,惯例是定义 close 方法并仅使用finalize进行健全性检查(即,如果尚未调用 close ,请立即执行此操作并记录错误。)
最近有产生深入讨论最终确定的问题 ,如果需要,应提供更多深度...
其他提示
查看 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
方法。
不,这里没有破坏者。原因是所有Java对象都是堆分配和垃圾回收。没有显式释放(即C ++的删除运算符),没有合理的方法来实现真正的析构函数。
Java确实支持终结器,但它们仅用作对象,用于保存对套接字,文件句柄,窗口句柄等本机资源句柄的对象。当垃圾收集器收集没有终结器的对象时,它只是标记内存区域是免费的,就是这样。当对象有一个终结器时,它首先被复制到一个临时位置(记住,我们这里是垃圾收集),然后它被排队到一个等待最终确定的队列中,然后一个Finalizer线程以非常低的优先级轮询队列并运行终结器。
当应用程序退出时,JVM会在不等待待定对象完成的情况下停止,因此几乎不能保证终结器能够运行。
应避免使用 finalize()方法。它们不是可靠的资源清理机制,可能会通过滥用垃圾收集器来引起垃圾收集器出现问题。
如果您需要在对象中进行释放调用,比如释放资源,请使用显式方法调用。可以在现有API中看到此约定(例如,可关闭, Graphics.dispose( ), Widget.dispose()),通常通过try / finally调用。
Resource r = new Resource();
try {
//work
} finally {
r.dispose();
}
尝试使用已处置的对象应该抛出运行时异常(请参阅 IllegalStateException异常)。
编辑:
我在想,如果我所做的只是 取消引用数据并等待 收集它们的垃圾收集器, 如果我的话,不会有内存泄漏 用户反复输入数据和 按下重置按钮?
通常,您需要做的就是取消引用对象 - 至少,这是它应该工作的方式。如果您担心垃圾收集,请查看 Java SE 6 HotSpot [tm]虚拟机垃圾收集调整(或JVM版本的等效文档)。
发布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...");
}
}
}
如果执行此类,则在 try
块被保留时, catch
之前将执行 c.close()
。 finally
块被执行。与 finalize()
方法的情况不同,保证执行 close()
。但是,不需要在 finally
子句中显式执行它。
我完全同意其他答案,并表示不要依赖执行finalize。
除了try-catch-finally块之外,您还可以使用运行时#addShutdownHook (在Java 1.3中引入)在程序中执行最终清理。
这与析构函数不同,但可以实现一个关闭挂钩,其中注册了侦听器对象,其中清理方法(关闭持久数据库连接,删除文件锁等)可以被调用 - 通常在析构函数中完成的东西。 再次 - 这不是析构函数的替代品,但在某些情况下,您可以使用此函数来处理所需的功能。
这样做的好处是可以从程序的其他部分解析行为松散耦合。
不, java .lang.Object#finalize
是你能得到的最接近的。
然而,当它(和如果)被调用时,不能保证。
请参阅: java.lang .Runtime#runFinalizersOnExit(布尔值)代码>
首先,请注意,由于 Java 是垃圾收集的,因此很少需要对对象销毁执行任何操作。首先,因为您通常没有任何托管资源可以释放,其次因为您无法预测何时或是否会发生,因此对于“一旦没有人再使用我的对象”需要发生的事情是不合适的”。
您可以在使用 java.lang.ref.PhantomReference 销毁对象后收到通知(实际上,说它已被销毁可能有点不准确,但如果对它的幻像引用排队,那么它就不再可恢复,这通常相当于一样的东西)。一个常见的用途是:
- 将类中需要析构的资源分离到另一个辅助对象中(请注意,如果您所做的只是关闭连接(这是常见情况),则无需编写新类:在这种情况下,要关闭的连接将是“辅助对象”)。
- 当您创建主对象时,还创建一个对其的 PhantomReference。要么让 this 引用新的辅助对象,要么设置从 PhantomReference 对象到其相应辅助对象的映射。
- 收集主对象后,PhantomReference 会排队(或者更确切地说,它可能会排队 - 就像终结器一样,不能保证它会排队,例如,如果 VM 退出,那么它不会等待)。确保您正在处理其队列(无论是在特殊线程中还是时不时地)。由于对辅助对象的硬引用,辅助对象尚未被收集。因此,对辅助对象进行任何您喜欢的清理,然后丢弃 PhantomReference,辅助对象最终也会被收集。
还有finalize(),它看起来像一个析构函数,但行为却不像。这通常不是一个好的选择。
如果这偏离主题,我很抱歉,但java.util.Timer(SE6)文档说:
"在对Timer对象的最后一次实时引用消失并且所有未完成的任务都已完成执行之后,计时器的任务执行线程正常终止(并且变为垃圾回收)。但是,这可能会发生任意长的时间。默认情况下,任务执行线程不作为守护程序线程运行,因此它能够阻止应用程序终止。如果调用者想要快速终止定时器的任务执行线程,则调用者应该调用定时器的取消方法......“
我想在拥有Timer失去最后一个引用(或之前的immeditalesky)的类上取消取消。在这里,一个可靠的析构函数可以为我做到这一点。上面的评论表明,最终是一个糟糕的选择,但有一个优雅的解决方案吗? “...能够使应用程序终止......”的业务。没有吸引力。
我同意大多数答案。
你不应该完全依赖任何一个 finalize
或者 ShutdownHook
JVM 不保证什么时候
finalize()
方法将被调用。finalize()
GC 线程仅调用一次。如果一个对象从终结方法中恢复过来,那么finalize
不会再被调用。在您的应用程序中,您可能有一些活动对象,这些对象永远不会调用垃圾收集。
任何
Exception
GC 线程会忽略由终结方法抛出的异常System.runFinalization(true)
和Runtime.getRuntime().runFinalization(true)
方法增加调用的概率finalize()
方法,但现在这两种方法已被弃用。由于缺乏线程安全性并可能产生死锁,这些方法非常危险。
public void addShutdownHook(Thread hook)
注册一个新的虚拟机关闭挂钩。
Java 虚拟机关闭以响应两种事件:
- 当最后一个非守护线程退出或退出时(等效地,
System.exit
) 方法被调用,或者 - 虚拟机会因响应用户中断(例如键入 ^C)或系统范围的事件(例如用户注销或系统关闭)而终止。
- 关闭钩子只是一个已初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩,并让它们同时运行。当所有挂钩完成后,如果启用了退出时终结,它将运行所有未调用的终结器。
- 最后,虚拟机将停止。请注意,守护线程将在关闭序列期间继续运行,如果通过调用 exit 方法启动关闭,则非守护线程也会继续运行。
关闭挂钩也应该快速完成其工作。当程序调用 exit 时,期望虚拟机将立即关闭并退出。
但即使是 Oracle 文档也引用了这一点
在极少数情况下,虚拟机可能会中止,即停止运行而不完全关闭
当虚拟机从外部终止时会发生这种情况,例如使用 SIGKILL
Unix 上的信号或 TerminateProcess
在 Microsoft Windows 上调用。如果本机方法出现错误,例如损坏内部数据结构或尝试访问不存在的内存,则虚拟机也可能中止。如果虚拟机中止,则无法保证是否会运行任何关闭挂钩。
结论 : 使用 try{} catch{} finally{}
适当地阻止并释放关键资源 finally(}
堵塞。在释放资源期间 finally{}
阻止、捕获 Exception
和 Throwable
.
如果只是记忆你担心,不要。只要相信GC,它就能做得很好。我实际上看到了一些关于它如此高效的东西,以至于在某些情况下创建大量微小对象比使用大型数组更好。
finalize()
函数是析构函数。
但是,通常不应该使用它,因为它在GC 之后被调用,你无法判断它何时会发生(如果有的话)。
此外,需要多个GC才能解除分配 finalize()
的对象。
您应该尝试使用 try {...} finally {...}
语句清理代码中的逻辑位置!
也许你可以使用try ... finally块来完成你正在使用该对象的控制流中的对象。当然它不会自动发生,但C ++中的破坏也不会发生。您经常会看到finally块中的资源关闭。
与Java中的析构函数最接近的是 finalize()方法。与传统析构函数的最大区别在于,您无法确定它何时被调用,因为这是垃圾收集器的责任。我强烈建议在使用它之前仔细阅读,因为文件句柄的典型RAIA模式等不能与finalize()一起可靠地工作。
如果您正在编写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().
显然不是你想要什么,但可能是其他人想要的。
在龙目岛有一个 @Cleanup 注释,大部分类似于C ++析构函数:
@Cleanup
ResourceClass resource = new ResourceClass();
处理它时(在编译时),Lombok会插入相应的 try-finally
块,以便在执行离开范围时调用 resource.close()
变量。您还可以明确指定另一种释放资源的方法,例如: resource.dispose()
:
@Cleanup("dispose")
ResourceClass resource = new ResourceClass();
虽然Java的GC技术已取得了相当大的进步,但仍需要注意您的参考资料。我想到了许多看似琐碎的参考模式的案例,这些模式实际上是老鼠筑巢。
从你的帖子来看,听起来你并没有尝试为了对象重用而实现重置方法(是吗?)。您的对象是否包含需要清理的任何其他类型的资源(即必须关闭的流,必须返回的任何池或借来的对象)?如果您唯一担心的是内存dealloc,那么我会重新考虑我的对象结构并尝试验证我的对象是自包含的结构,将在GC时间清理。
只考虑原来的问题......我认为我们可以从所有其他学到的答案中得出结论,也可以从Bloch的基本问题中得出结论 Effective Java ,第7项,”避免终结者“,以不适合Java语言的方式寻求合法问题的解决方案......:
...不是一个非常明显的解决方案来做OP真正想要的是保持所有需要在某种“playpen”中重置的对象,所有其他不可重置对象都有引用只能通过某种访问者对象......
然后当你需要“重置”时你断开了现有的围栏并建立了一个新的围栏:围栏中的所有物体网都是漂流,永远不会返回,有一天由GC收集。
如果这些对象中的任何一个是 Closeable
(或者没有,但是有 close
方法),你可以把它们放在 Bag
中。在创建(并且可能已打开)的围栏,并且在切断围栏之前访问者的最后一个行为是通过所有 Closeables
来关闭它们??
代码可能看起来像这样:
accessor.getPlaypen().closeCloseables();
accessor.setPlaypen( new Playpen() );
closeCloseables
可能是一个阻塞方法,可能涉及一个锁存器(例如 CountdownLatch
),以处理(并等待)任何 Runnables
/ Callables
在任何特定于 Playpen
的线程中,视情况而定,特别是在JavaFX线程中。
Java中没有确切的析构函数类,垃圾收集器会自动在java中销毁类。但你可以使用下面的一个来做到这一点,但它不完全相同:
<强>完成()强>
有产生深入讨论最终确定的问题,所以如果需要你应该得到更多的深度...
我过去主要处理C ++,这也是导致我搜索析构函数的原因。我现在正在使用JAVA。我做了什么,对于每个人来说可能不是最好的情况,但我通过将所有值重置为0或通过函数默认来实现我自己的析构函数。
示例:
public myDestructor() {
variableA = 0; //INT
variableB = 0.0; //DOUBLE & FLOAT
variableC = "NO NAME ENTERED"; //TEXT & STRING
variableD = false; //BOOL
}
理想情况下,这不适用于所有情况,但只要你没有大量的全局变量就会有效。
我知道我不是最好的Java程序员,但它似乎对我有用。