ChatGPT解决这个技术问题 Extra ChatGPT

Java有析构函数吗?

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

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

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

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

如果您持有对您不需要的对象的引用,则只有内存泄漏。即您的程序中有一个错误。 GC 将根据需要运行(有时更快)
如果您通过对象快速处理数据,虚拟机将不会很快运行 GC。认为 GC 总能跟上,或做出正确决定的想法是错误的。
@Kieveli 在给出错误之前JVM不会运行GC吗?
是的,如果有 Java 的析构函数可以彻底销毁它,那就太好了。
@WVrock - 有趣的问题。答案是“不”(至少对于某些类型的“通过对象快速处理数据”),但原因很微妙。当您将 97% 的时间花在垃圾收集上,而只有 3% 的时间花在实际程序逻辑上时,就会发生实际错误,因为大多数引用仍然有指向它们的指针。如果“快速处理”使用少量指针不会有问题。

C
Community

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

有一个称为 finalize 的继承方法,但这完全由垃圾收集器决定调用。所以对于需要明确整理的类,约定是定义一个 close 方法并使用 finalize 仅用于完整性检查(即如果 close 没有被调用,现在就做并记录错误)。

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


这个上下文中的“close()”是指 java.lang.Autocloseable 中的方法吗?
不,AutoCloseable 是在 Java 7 中引入的,但“close()”约定的存在时间要长得多。
为什么您无法预测何时(或什至)对象将被销毁。有什么近似的其他方法可以预测?
@dctremblay 对象销毁由垃圾收集器完成,垃圾收集器可能永远不会在应用程序的生命周期内运行。
请注意,Java 9 中的 finalize 方法 has been deprecated
V
Vitalii Fedorenko

查看 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-with-resources 将无济于事。除非可以以所述方法被调用的速率关闭和重新打开所述资源 - 这不是一般事实。
事实上,这不是真正的答案。除非对象的构造和使用完全被 try 封装并且 finally 用于强制调用 obj.finalize(),否则无法使用此结构来管理对象的销毁。甚至这种设置也不能解决 OP 带来的问题:由“重置”按钮触发的对象销毁中间程序。
其他用户已在您的应用程序入口点显示此操作。全局定义变量。在入口函数中初始化它,用try。在 finally 上取消初始化(当您的应用程序关闭时)。这是完全可能的。
@nurettin Java 7 在被问到这个问题时才发布了 3 个月,如果这有助于更理解它。
d
ddimitrov

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

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

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


感谢您提及本机资源 - 这是“类似析构函数”方法有用的一个领域。
是的,我现在正面临同样的问题,即释放通过对 C++ 的本机调用分配的资源/句柄。
@ddimitrov,理论上java可以实现显式释放吗?或者这是一个逻辑矛盾?
@mils 天真地实现显式释放会破坏 Java 的假设,即任何引用都指向活动对象。您可以遍历所有指针并将别名归零,但这比 GC 更昂贵。或者你可以尝试使用一些线性类型系统(参见 Rust 中的“所有权”),但这是一个重大的语言变化。还有其他选项(请参阅 JavaRT 范围内存等),但通常显式释放并不适合 Java 语言。
M
McDowell

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


这不是取消引用的意思。它不是“将对象的最后一个引用设置为 null”,而是从引用中获取(读取)值,以便您可以将其用于后续操作。
try..finally 仍然是一种有效且推荐的方法吗?假设我之前在 finalize() 中调用了本地方法,我可以将调用移至 finally 子句吗? class Resource { finalize() { destroy(); } protected native void destroy(); } class Alt_Resource { try (Resource r = new Resource()) { // use r } finalize { r.destroy(); }
r 不会被限制在 finally 块中。因此,此时您不能调用destroy。现在,如果您更正范围以在 try 块之前创建对象,那么您最终会遇到丑陋的“在 try-with-resources 之前”的情况。
N
Nikolas Charalambidis

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


如果我们不使用 try-with-resources 块怎么办?我认为我们可以在 finalize() 中调用 close 以确保已调用 close。
@shintoZ 正如我在上面的答案中所读到的,不能保证 finalize() 的执行
A
Agi Hammerthief

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

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

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

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


addShutdownHook 显然是在 Java 1.3 中引入的。无论如何,它在 1.5 中可供我使用。 :) 看到这个:stackoverflow.com/questions/727151/…
仅供参考,根据我的经验,如果您在 Eclipse 中使用红色的“终止”按钮,则不会调用关闭挂钩 - 整个 JVM 会立即被销毁,关闭挂钩不会被优雅地调用。这意味着如果您使用 eclipse 进行开发,您可能会在开发和生产过程中看到不同的行为
M
Mr. Polywhirl

不,java.lang.Object#finalize 是您可以获得的最接近的值。

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


在我的书中,可能会或可能不会被调用的方法本质上是无用的。最好不要用一种无用的特殊方法污染语言,这种方法充其量只会给人一种虚假的安全感。我永远无法理解为什么 Java 语言的开发人员认为 finalize 是一个好主意。
@antred The developers of the Java language agree。我想,当时,对于其中一些人来说,这是他们第一次设计带有垃圾收集的编程语言和运行时环境。不太容易理解的是,为什么在已经理解这个概念是一个坏主意的时候,同时使用其他托管语言 copied that concept
C
Community

我同意大多数答案。

您不应完全依赖 finalizeShutdownHook

finalize

JVM 不保证何时调用此 finalize() 方法。 finalize() 只被 GC 线程调用一次。如果一个对象从 finalizing 方法中恢复过来,那么 finalize 将不会被再次调用。在您的应用程序中,您可能有一些活动对象,它们永远不会调用垃圾收集。 finalizing 方法抛出的任何异常都会被 GC 线程忽略 System.runFinalization(true) 和 Runtime.getRuntime().runFinalization(true) 方法增加了调用 finalize() 方法的概率,但现在这两种方法已被弃用.由于缺乏线程安全性和可能产生死锁,这些方法非常危险。

shutdownHooks

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{} 块中释放资源期间,捕获 ExceptionThrowable


S
Steve Jessop

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

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

将类中需要销毁的资源分离到另一个辅助对象中(请注意,如果您所做的只是关闭连接,这是一种常见情况,则无需编写新类:在这种情况下,要关闭的连接将是“帮助对象”)。

当您创建主对象时,还要为其创建一个 PhantomReference。要么让 this 引用新的辅助对象,要么设置从 PhantomReference 对象到它们对应的辅助对象的映射。

收集到主要对象后,PhantomReference 被排队(或者更确切地说,它可能被排队 - 像终结器一样,不能保证它永远是,例如如果 VM 退出,那么它不会等待)。确保您正在处理其队列(在特殊线程中或不时处理)。由于对辅助对象的硬引用,辅助对象尚未被收集。所以对辅助对象做任何你喜欢的清理,然后丢弃 PhantomReference,辅助对象最终也会被收集。

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


为什么使用 PhantomReference 而不是 WeakReference?
@uckelman:如果您想要的只是通知,那么 PhantomReference 就可以完成这项工作,这几乎就是它的设计目的。此处不需要 WeakReference 的附加语义,并且在通知您的 ReferenceQueue 时,您无法再通过 WeakReference 恢复对象,因此使用它的唯一原因是不必记住 PhantomReference 存在。 WeakReference 所做的任何额外工作可能都可以忽略不计,但为什么要为此烦恼呢?
感谢您暗示 PhantomReference。它并不完美,但总比没有好。
@SteveJessop 与幻像引用相比,您认为什么“额外工作”具有弱引用?
S
Sai Kishore

finalize() 函数是析构函数。

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

此外,释放具有 finalize() 的对象需要不止一次 GC。

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


J
John Nilsson

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


I
I GIVE CRAP ANSWERS

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


当有问题的资源只有一个所有者并且从未有对它的引用被其他代码“窃取”时,这是正确的答案。
A
Alexey

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

@Cleanup
ResourceClass resource = new ResourceClass();

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

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

我看到的优点是嵌套更少(如果您有很多需要“销毁”的对象,这可能很重要)。
一个 try-with-resource 块可以一次性拥有多个资源
但是它们之间不能有指令。
公平的。我想那么建议是更喜欢 try-with-resource,即使对于多个资源,除非它们之间需要有指令迫使您创建新的 try-with-resource 块(增加嵌套),然后使用 @Cleanup
N
Nik

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


m
mike rodent

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

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

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

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

代码可能看起来像这样:

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

closeCloseables 可能是一种阻塞方法,可能涉及锁存器(例如 CountdownLatch),用于处理(并酌情等待)特定于 Playpen 的任何线程中的任何 Runnables/Callables酌情结束,特别是在 JavaFX 线程中。


K
Kaan

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

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

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

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

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


佚名

如果您正在编写 Java Applet,则可以覆盖 Applet 的“destroy()”方法。这是...

* 由浏览器或小程序查看器调用以通知 * 此小程序它正在被回收并且它应该销毁 * 它已分配的任何资源。 stop() 方法 * 总是在 destroy() 之前调用。

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


M
Markus Schulte

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

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

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

t
tyler

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

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


A
Anubhav Mishra

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


A
Ayesha

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


T
Thejas Naik

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


s
swpalmer

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

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

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


M
Manas Kumar Maharana

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

完成()

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


C
ChristianGLong

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

例子:

public myDestructor() {

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

}

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

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


尝试更多地使用不可变对象,在你“得到它”之后它会更有意义:)
这并没有太大的错误,因为它毫无意义——即一无所获。如果您的程序需要重置原始类型才能正常工作,那么您的类实例的范围不正确,这意味着您可能正在将现有对象的属性重新分配给新对象的属性,而没有创建新的 Object()。
除了有很多需要重置变量。我选择了析构函数这个名字,因为它适合我正在做的事情。它确实取得了一些成就,只是没有你能理解