Java有析构函数吗?

Java有析构函数吗?我好像找不到任何关于这个的文件。如果没有,我怎样才能达到同样的效果?

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

作为一个通常的C/ c++程序员,我认为这是微不足道的实现。(因此我打算最后实现它。)我构造了我的程序,使所有“可重置”的对象都在同一个类中,这样当按下重置按钮时,我就可以销毁所有“活动”对象。

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

657467 次浏览

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

然而,当(如果)它被调用时,是不保证的 看到:# EYZ1 < / p >

finalize()函数是析构函数。

但是,它不应该被正常使用,因为它被调用垃圾收集后,并且您无法判断何时会发生(如果有的话)。

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

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

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

有一个名为finalize的继承方法,但它完全由垃圾回收器自行调用。因此,对于需要显式清理的类,约定是定义一个关闭方法,并仅使用finalize方法进行完整性检查(即,如果关闭未被调用,则立即执行该操作并记录错误)。

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

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

不,这里没有析构函数。原因是所有Java对象都是堆分配和垃圾收集。没有显式的释放(即c++的delete操作符),就没有实现真正析构函数的合理方法。

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

当应用程序退出时,JVM停止而不等待挂起的对象完成,因此实际上不能保证终结器将运行。

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

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

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

尝试使用已处置对象应该抛出运行时异常(参见IllegalStateException)。


编辑:

我在想,如果我所做的只是 来解除对数据的引用并等待 垃圾收集器来收集它们, 会不会有内存泄漏,如果我的 用户反复输入数据和 按了重置键?< / p >

一般来说,您所需要做的就是取消对对象的引用——至少,这是它应该工作的方式。如果您担心垃圾收集,请查看Java SE 6 HotSpot[tm]虚拟机垃圾收集调优(或您的JVM版本的等效文档)。

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

我完全同意其他答案,说不依赖执行的最后确定。

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

这和析构函数不一样,但是可以实现一个关机钩子,在该钩子上注册侦听器对象,可以调用清理方法(关闭持久数据库连接,删除文件锁等)—通常会在析构函数中完成. 这和析构函数不一样。 再次强调,这不是析构函数的替代品,但在某些情况下,您可以使用此函数来接近所需的功能

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

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

你可以使用java.lang.ref.PhantomReference在一个对象被销毁后得到通知(实际上,说它已经被销毁可能有点不准确,但如果对它的幻影引用处于队列中,那么它就不再是可恢复的,这通常是一样的)。常见的用法是:

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

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

如果你只是担心记忆,那就别担心了。相信GC,它做得不错。实际上,我发现它非常高效,在某些情况下,创建一堆小对象比使用大数组的性能更好。

尽管Java的GC技术已经有了相当大的进步,但您仍然需要注意引用。我想到了许多看起来微不足道的参考模式的例子,它们实际上是罩下的老鼠窝。

从你的文章中,听起来你并不是为了对象重用而试图实现一个reset方法(真的吗?)您的对象是否持有需要清理的其他类型的资源(例如,必须关闭的流,必须返回的任何池化或借来的对象)?如果您唯一担心的是内存dealloc,那么我将重新考虑我的对象结构,并尝试验证我的对象是自包含的结构,将在GC时被清理。

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

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

看一下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 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子句中显式地执行它。

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

例子:

public myDestructor() {


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


}

理想情况下,这并不适用于所有情况,但在有全局变量的情况下,只要你没有大量的全局变量,它就可以工作。

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

我同意大部分答案。

你不应该完全依赖finalizeShutdownHook

finalize

  1. JVM不保证什么时候调用finalize()方法。

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

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

  4. 任何由终结方法抛出的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. shutdown钩子只是一个初始化但未启动的线程。当虚拟机开始关机顺序时,它将以某种未指定的顺序启动所有已注册的关机钩子,并让它们并发运行。当所有钩子都完成时,它将运行所有未调用的终结器(如果退出时终结已启用)。

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

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

    但就连Oracle文档也引用了这句话

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

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

# eyz5: # eyz6

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

... OP实际上想要的是将所有需要重置的对象保存在一种“playpen”中,而所有其他不可重置的对象只能通过某种访问器对象引用该对象,这难道不是一个非常明显的解决方案吗?

然后当你需要“重置”时,你断开现有的游戏笔并创建一个新的游戏笔:游戏笔中所有的对象都被抛到一边,永远不会返回,有一天会被GC收集。

如果这些对象中的任何一个是Closeable(或者不是,但有一个close方法),你可以把他们放在一个Bag在游戏笔,因为他们被创建(可能打开),访问器在切断游戏笔之前的最后一个动作将是通过所有的Closeables关闭他们…?

代码可能看起来像这样:

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

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

在Lombok中有一个@Cleanup注释,它非常类似于c++的析构函数:

@Cleanup
ResourceClass resource = new ResourceClass();

在处理它时(在编译时),Lombok插入适当的try-finally块,以便在执行离开变量的作用域时调用resource.close()。你也可以明确地指定另一个方法来释放资源,例如resource.dispose():

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

在Java中没有确切的析构函数类,在Java中由垃圾收集器自动销毁的类。但你也可以用下面的方法,但这不是一回事:

finalize ()

最后一个问题引发了深入的讨论,所以如果需要的话你可以获得更多的深度……

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

如果JVM由于System.exit()Runtime.getRuntime().exit()而退出,终止器将默认不运行。从# EYZ2:

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

您可以调用System.runFinalization(),但它只会“尽最大努力完成所有未完成的终结”—并不能保证。

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

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

在Java中,垃圾收集器自动删除未使用的对象以释放内存。所以Java没有析构函数是很合理的。

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

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

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

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

如果你有机会使用上下文和依赖注入(CDI)框架,比如焊接,你可以使用Java注释@Predestroy来做清理工作等。

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


@javax.annotation.PreDestroy
public void cleanup() {
// do your cleanup
}
}