Java有析构函数吗?我似乎无法找到任何有关此的文档。如果没有,我怎样才能达到同样的效果?
为了使我的问题更具体,我正在编写一个处理数据的应用程序,并且规范说应该有一个“重置”按钮,可以将应用程序恢复到其刚启动的原始状态。但是,除非应用程序关闭或按下重置按钮,否则所有数据都必须是“实时的”。
通常作为 C/C++ 程序员,我认为这很容易实现。 (因此我计划最后实现它。)我构建了我的程序,使所有“可重置”对象都在同一个类中,这样我就可以在按下重置按钮时销毁所有“活动”对象。
我在想如果我所做的只是取消引用数据并等待垃圾收集器收集它们,如果我的用户反复输入数据并按下重置按钮,会不会出现内存泄漏?我也在想,既然 Java 作为一门语言已经相当成熟,应该有一种方法可以防止这种情况发生或优雅地解决这个问题。
因为 Java 是一种垃圾收集语言,所以您无法预测对象何时(或什至)会被销毁。因此,没有直接等效的析构函数。
有一个称为 finalize
的继承方法,但这完全由垃圾收集器决定调用。所以对于需要明确整理的类,约定是定义一个 close 方法并使用 finalize 仅用于完整性检查(即如果 close 没有被调用,现在就做并记录错误)。
最近有 a question that spawned in-depth discussion of finalize,所以如果需要的话应该提供更多的深度......
查看 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
封装并且 finally
用于强制调用 obj.finalize()
,否则无法使用此结构来管理对象的销毁。甚至这种设置也不能解决 OP 带来的问题:由“重置”按钮触发的对象销毁中间程序。
不,这里没有析构函数。原因是所有 Java 对象都是堆分配和垃圾收集的。如果没有显式释放(即 C++ 的删除运算符),就没有明智的方法来实现真正的析构函数。
Java 确实支持终结器,但它们仅用于保护持有本地资源(如套接字、文件句柄、窗口句柄等)句柄的对象。当垃圾收集器收集没有终结器的对象时,它只是标记内存区域免费,仅此而已。当对象有终结器时,它首先被复制到一个临时位置(请记住,我们在这里进行垃圾收集),然后将其排入等待终结队列,然后终结器线程以非常低的优先级轮询队列并运行终结器。
当应用程序退出时,JVM 会停止而无需等待未决对象完成,因此实际上无法保证您的终结器将永远运行。
应避免使用 finalize() 方法。它们不是一种可靠的资源清理机制,滥用它们可能会导致垃圾收集器出现问题。
如果您需要在对象中进行释放调用,例如释放资源,请使用显式方法调用。这种约定可以在现有的 API(例如 Closeable、Graphics.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 版本的等效文档)。
class Resource { finalize() { destroy(); } protected native void destroy(); } class Alt_Resource { try (Resource r = new Resource()) { // use r } finalize { r.destroy(); }
随着 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
块离开时执行,并且在 catch
和 finally
块执行之前执行。与 finalize()
方法不同,close()
保证被执行。但是,不需要在 finally
子句中显式执行它。
finalize()
的执行
我完全同意其他答案,说不依赖finalize的执行。
除了 try-catch-finally 块之外,您还可以使用 Runtime#addShutdownHook(在 Java 1.3 中引入)在您的程序中执行最终清理。
这与析构函数不同,但可以实现一个关闭钩子,其中注册了监听器对象,可以调用清理方法(关闭持久数据库连接、删除文件锁等)——通常会在析构函数。再说一遍 - 这不是析构函数的替代品,但在某些情况下,您可以使用它来实现所需的功能。
这样做的好处是解构行为与程序的其余部分松散耦合。
不,java.lang.Object#finalize
是您可以获得的最接近的值。
但是,不能保证何时(以及是否)调用它。
请参阅:java.lang.Runtime#runFinalizersOnExit(boolean)
我同意大多数答案。
您不应完全依赖 finalize
或 ShutdownHook
JVM 不保证何时调用此 finalize() 方法。 finalize() 只被 GC 线程调用一次。如果一个对象从 finalizing 方法中恢复过来,那么 finalize 将不会被再次调用。在您的应用程序中,您可能有一些活动对象,它们永远不会调用垃圾收集。 finalizing 方法抛出的任何异常都会被 GC 线程忽略 System.runFinalization(true) 和 Runtime.getRuntime().runFinalization(true) 方法增加了调用 finalize() 方法的概率,但现在这两种方法已被弃用.由于缺乏线程安全性和可能产生死锁,这些方法非常危险。
public void addShutdownHook(Thread hook)
注册一个新的虚拟机关闭挂钩。
Java 虚拟机关闭以响应两种事件:
程序正常退出,当最后一个非守护线程退出,或者调用了exit(相当于System.exit)方法,或者虚拟机响应用户中断而终止,比如输入^C,或者系统-wide 事件,例如用户注销或系统关闭。关闭挂钩只是一个已初始化但未启动的线程。当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩并让它们同时运行。当所有钩子都完成后,如果 finalization-on-exit 已启用,它将运行所有未调用的终结器。最后,虚拟机将停止。请注意,如果关闭是通过调用 exit 方法启动的,则守护线程将在关闭序列期间继续运行,非守护线程也将继续运行。关闭挂钩也应该快速完成它们的工作。当程序调用 exit 时,期望虚拟机将立即关闭并退出。但即使是 Oracle 文档也引用了这一点
在极少数情况下,虚拟机可能会中止,即在没有干净关闭的情况下停止运行
当虚拟机在外部终止时会发生这种情况,例如 Unix 上的 SIGKILL
信号或 Microsoft Windows 上的 TerminateProcess
调用。如果本地方法出错,例如破坏内部数据结构或尝试访问不存在的内存,虚拟机也可能中止。如果虚拟机中止,则无法保证是否会运行任何关闭挂钩。
结论:适当使用 try{} catch{} finally{}
块并释放 finally(}
块中的关键资源。在 finally{}
块中释放资源期间,捕获 Exception
和 Throwable
。
首先,请注意,由于 Java 是垃圾收集的,因此很少需要对对象销毁做任何事情。首先是因为您通常没有任何可释放的托管资源,其次是因为您无法预测何时或是否会发生,因此对于您需要发生的事情“一旦没有人再使用我的对象”是不合适的”。
可以在使用 java.lang.ref.PhantomReference 销毁对象后通知您(实际上,说它已被销毁可能有点不准确,但如果对它的幻像引用排队,则它不再可恢复,这通常相当于同样的事情)。一个常见的用途是:
将类中需要销毁的资源分离到另一个辅助对象中(请注意,如果您所做的只是关闭连接,这是一种常见情况,则无需编写新类:在这种情况下,要关闭的连接将是“帮助对象”)。
当您创建主对象时,还要为其创建一个 PhantomReference。要么让 this 引用新的辅助对象,要么设置从 PhantomReference 对象到它们对应的辅助对象的映射。
收集到主要对象后,PhantomReference 被排队(或者更确切地说,它可能被排队 - 像终结器一样,不能保证它永远是,例如如果 VM 退出,那么它不会等待)。确保您正在处理其队列(在特殊线程中或不时处理)。由于对辅助对象的硬引用,辅助对象尚未被收集。所以对辅助对象做任何你喜欢的清理,然后丢弃 PhantomReference,辅助对象最终也会被收集。
还有finalize(),它看起来像一个析构函数,但行为不像一个析构函数。这通常不是一个好的选择。
finalize()
函数是析构函数。
但是,通常不应该使用它,因为它是在 GC 之后调用的,并且您无法确定何时会发生这种情况(如果有的话)。
此外,释放具有 finalize()
的对象需要不止一次 GC。
您应该尝试使用 try{...} finally{...}
语句清理代码中的逻辑位置!
如果您担心的只是记忆,请不要。只要相信 GC,它做得不错。实际上,我看到它是如此高效,以至于在某些情况下,创建小对象堆比使用大型数组更能提高性能。
也许您可以使用 try ... finally 块来最终确定您正在使用该对象的控制流中的对象。当然它不会自动发生,但 C++ 中的破坏也不会。您经常会在 finally 块中看到资源关闭。
Lombok 中有一个 @Cleanup 注释,主要类似于 C++ 析构函数:
@Cleanup
ResourceClass resource = new ResourceClass();
在处理它时(在编译时),Lombok 插入适当的 try-finally
块,以便在执行离开变量范围时调用 resource.close()
。您还可以显式指定另一种释放资源的方法,例如 resource.dispose()
:
@Cleanup("dispose")
ResourceClass resource = new ResourceClass();
@Cleanup
与 Java 中的析构函数最接近的是 finalize() 方法。与传统析构函数的最大区别在于您无法确定何时调用它,因为这是垃圾收集器的职责。我强烈建议您在使用它之前仔细阅读此内容,因为文件句柄等的典型 RAIA 模式无法与 finalize() 一起可靠地工作。
想想最初的问题……我认为我们可以从所有其他学到的答案中得出结论,也可以从 Bloch 的基本 Effective Java 第 7 项“避免终结器”中得出结论,以某种方式寻求合法问题的解决方案这不适合 Java 语言...:
...不会是一个非常明显的解决方案来做OP实际想要的是将所有需要重置的对象保留在一种“游戏笔”中,所有其他不可重置的对象只能通过某种方式引用访问器对象...
然后,当您需要“重置”时,您断开现有的围栏并制作一个新围栏:围栏中的所有对象网络都被抛在一边,永远不会返回,并且有一天会被 GC 收集。
如果这些对象中的任何一个是 Closeable
(或不是,但具有 close
方法),您可以在创建(并可能打开)它们时将它们放在围栏中的 Bag
中,以及访问器的最后一个动作在切断围栏之前将通过所有 Closeables
关闭它们......?
代码可能看起来像这样:
accessor.getPlaypen().closeCloseables();
accessor.setPlaypen( new Playpen() );
closeCloseables
可能是一种阻塞方法,可能涉及锁存器(例如 CountdownLatch
),用于处理(并酌情等待)特定于 Playpen
的任何线程中的任何 Runnables
/Callables
酌情结束,特别是在 JavaFX 线程中。
这里有很多很好的答案,但还有一些关于为什么应该避免使用 finalize() 的附加信息。
如果 JVM 由于 System.exit()
或 Runtime.getRuntime().exit()
而退出,默认情况下不会运行终结器。从 Javadoc for Runtime.exit():
虚拟机的关闭顺序包括两个阶段。在第一阶段,所有已注册的关闭挂钩(如果有)都以某种未指定的顺序启动,并允许同时运行直到它们完成。在第二阶段,如果 finalization-on-exit 已启用,则所有未调用的终结器都会运行。完成此操作后,虚拟机将停止。
您可以调用 System.runFinalization()
,但它只会“尽最大努力完成所有未完成的最终确定”—— 不是保证。
有一个 System.runFinalizersOnExit()
方法,但不要使用它——它不安全,很久以前就被弃用了。
如果您正在编写 Java Applet,则可以覆盖 Applet 的“destroy()”方法。这是...
* 由浏览器或小程序查看器调用以通知 * 此小程序它正在被回收并且它应该销毁 * 它已分配的任何资源。 stop() 方法 * 总是在 destroy() 之前调用。
显然不是你想要的,但可能是其他人正在寻找的。
如果您有机会使用 上下文和依赖注入 (CDI) 框架,例如 Weld,您可以使用 Java 注释 @Predestroy 进行清理工作等。
@javax.enterprise.context.ApplicationScoped
public class Foo {
@javax.annotation.PreDestroy
public void cleanup() {
// do your cleanup
}
}
尽管 Java 的 GC 技术已经取得了相当大的进步,但您仍然需要注意您的引用。脑海中浮现出许多看似微不足道的参考模式实际上是老鼠窝的案例。
从您的帖子看来,您似乎并没有试图实现一个重置方法以实现对象重用(真的吗?)。您的对象是否拥有需要清理的任何其他类型的资源(即必须关闭的流、必须归还的任何池化或借用对象)?如果您唯一担心的是内存释放,那么我会重新考虑我的对象结构并尝试验证我的对象是自包含结构,将在 GC 时清理。
没有Java没有任何析构函数。Java中它背后的主要原因是垃圾收集器始终在后台被动工作,所有对象都在堆内存中生成,这就是GC工作的地方。在c ++中,我们必须显式调用删除函数,因为没有类似垃圾收集器的东西。
在 Java 中,垃圾收集器会自动删除未使用的对象以释放内存。因此,Java 没有可用的析构函数是明智的。
当涉及到 android 编程时,尝试调用 onDestroy() 方法。这是在 Activity/Service 类被终止之前执行的最后一个方法。
缺少我刚刚扫描的所有答案是终结器的更安全替代品。关于使用 try-with-resources 和避免终结器,所有其他答案都是正确的,因为它们不可靠并且现在已弃用......
但是他们没有提到清洁工。 Java 9 中添加了清理器,以比终结器更好的方式显式处理清理工作。
https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html
Java中没有完全的析构函数类,java中的类被垃圾收集器自动销毁。但你可以使用下面的一个来做到这一点,但它并不完全相同:
完成()
有 a question that spawned in-depth discussion of finalize ,因此如果需要,您应该获得更多深度......
我过去主要处理 C++,这也是我寻找析构函数的原因。我现在经常使用 JAVA。我做了什么,它可能不是对每个人来说都是最好的情况,但我通过一个函数将所有值重置为 0 或默认值来实现我自己的析构函数。
例子:
public myDestructor() {
variableA = 0; //INT
variableB = 0.0; //DOUBLE & FLOAT
variableC = "NO NAME ENTERED"; //TEXT & STRING
variableD = false; //BOOL
}
理想情况下,这并不适用于所有情况,但是在存在全局变量的情况下,只要您没有大量变量,它就会起作用。
我知道我不是最好的 Java 程序员,但它似乎对我有用。
finalize
方法 has been deprecated。