为什么java和c#中有终结器?

时间:2009-12-05 01:24:43

标签: c# java language-features finalizer

我不太明白为什么有java和c#等语言的终结器。 AFAIK,他们:

  • 不保证可以运行(在java中)
  • 如果他们确实在运行,他们可能会在有问题的对象成为最终确定候选人之后运行一段任意时间
  • 和(至少在java中),即使坚持上课,也会产生惊人的巨大性能。

那他们为什么要添加呢?我问了一个朋友,他嘟something了一些关于“你想尽可能地清理像数据库连接这样的东西”的东西,但这对我来说是一种不好的做法。你为什么要依赖具有任何的上述属性的东西,即使作为最后一道防线?特别是当如果在任何API中设计了类似的东西时,所述API就会被笑掉。

9 个答案:

答案 0 :(得分:16)

嗯,在某些情况下,它们非常有用。

在.NET CLR中,例如:

  
      
  • 不保证能够运行
  •   

如果程序未被杀死,终结器将始终最终运行。关于它什么时候运行它只是不确定。

  
      
  • 如果他们确实在运行,他们可能会在有问题的对象成为最终确定候选人之后运行一段任意时间
  •   

但确实如此,它们仍在运行。

在.NET中,这非常非常有用。在.NET中将原生的非.NET资源包装到.NET类中是很常见的。通过实现终结器,您可以保证正确清理本机资源。如果没有这个,用户将被迫调用一个方法来执行清理,这会大大降低垃圾收集器的效率。

通过实施终结器来确定何时释放您的(本机)资源并不总是很容易,即使您的课程以不太完美的方式使用,您也可以保证它们能够得到正确的清理。

  
      
  • 和(至少在java中),即使坚持上课,也会产生惊人的巨大性能打击
  •   

同样,.NET CLR的GC在这方面具有优势。如果您实现了正确的接口(IDisposable),那么如果开发人员正确实现了该接口,则可以防止发生最终的昂贵部分。这样做的方法是,用户定义的清理方法可以调用GC.SuppressFinalize,绕过终结器。

这为您提供了两全其美的优势 - 您可以实现终结器和IDisposable。如果您的用户正确处理您的对象,则终结器没有任何影响。如果他们不这样做,终结器(最终)将运行并清理您的非托管资源,但在运行时会遇到(小)性能损失。

答案 1 :(得分:10)

Hmya,你得到的照片画得有点过于乐观。终结器也不能保证在.NET中运行。典型的不幸是终结器,它会在终结器线程上抛出异常或超时(2秒)。

当Microsoft决定在SQL Server中提供.NET托管支持时,这是一个问题。重新启动应用程序以解决资源泄漏的应用程序类型不被视为可行的解决方法。 .NET 2.0获得了关键终结器,通过派生CriticalFinalizerObject类来实现。这样一个类的终结器必须遵守约束执行区(CERs)的规则,本质上是一个禁止异常的代码区域。你可以在CER中做的事情非常有限。

回到原来的问题,终结器是释放内存以外的操作系统资源所必需的。垃圾收集器管理内存非常好,但没有做任何事情来释放笔,画笔,文件,套接字,窗口,管道等。当一个对象使用这样的资源时,它必须确保在完成后释放资源它。即使程序忘记了,终结器也能确保发生这种情况。你几乎从不用自己的终结器写一个类,操作资源由框架中的类包装。

.NET框架还有一个编程模式,以确保尽早释放这样的资源,以便在终结器运行之前资源不会停留。所有具有终结器的类也实现了IDisposable.Dispose()方法,允许您的代码显式释放资源。这通常被.NET程序员所遗忘,但这通常不会导致问题,因为终结器确保最终完成。许多.NET程序员已经失去了几个小时的睡眠,担心是否所有的Dispose()调用都得到了解决,并且大量的线程已经在论坛上启动了。 Java人员必须更开心。


跟进您的评论:终结器线程中的异常和超时是您不必担心的。首先,如果你发现自己正在写一个终结者,请深呼吸,问问自己是否在正确的道路上。终结器适用于框架类,您应该使用这样的类来使用操作资源,您将免费获得内置于该类的终结器。一直到SafeHandle类,他们都有一个关键的终结器。

其次,终结器线程故障是严重的程序故障。类似于获取OutOfMemory异常或绊倒电源线并拔出机器。除了修复代码中的错误或重新布线之外,您无法对它们做任何事情。微软设计关键终结器非常重要,他们不能依赖为SQL Server编写.NET代码的所有程序员来正确获取代码。如果你自己摸索终结者,那么没有这样的责任,那就是你接到客户的电话,而不是微软。

答案 2 :(得分:4)

如果您读取JavaDoc for finalize(),它表示“当垃圾收集确定没有对该对象的更多引用时,由对象上的垃圾收集器调用。子类重写finalize方法以处置系统资源或者进行其他清理。“

http://java.sun.com/javase/6/docs/api/java/lang/Object.html#finalize

这就是“为什么”。我想你可以争论他们的实施是否有效。

我发现finalize()的最佳用途是通过释放池化资源来检测错误。大多数泄露的对象最终会被垃圾收集,您可以生成调试信息。

class MyResource {
    private Throwable allocatorStack;

    public MyResource() {
        allocatorStack = new RuntimeException("trace to allocator");
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            System.out.println("Bug!");
            allocatorStack.printStackTrace();
        } finally {
            super.finalize();
        }
    }
 }

答案 3 :(得分:4)

在java中,存在终结器以允许清理外部资源(存在于JVM外部的东西,并且当'父'java对象存在时不能被垃圾收集)。这一直是罕见的。例如,如果您正在与某些自定义硬件连接。

我认为java中的终结器无法保证运行的原因是它们可能没有机会在程序终止时这样做。

使用“纯”java中的终结器可能会做的一件事就是用它来测试终止条件 - 例如检查所有连接是否已关闭,如果不是则报告错误。您无法保证始终会捕获错误,但可能至少会在某些时间内捕获到足以显示错误的错误。

大多数java代码都没有调用终结器。

答案 4 :(得分:2)

它们用于释放在对象的所有引用被破坏之前无法释放的本机资源(例如套接字,打开文件,设备),这是特定的调用者(通常)无法知道。替代方案是微妙的,不可能追踪的资源泄漏......

当然,在很多情况下,作为应用程序作者知道只有一个对DB连接的引用(例如);在这种情况下,当你知道你已经完成它时,终结器不能替代它。

答案 5 :(得分:0)

在.Net land中,无法保证 运行时。但他们会跑。

答案 6 :(得分:0)

您是在引用Object.Finalize吗?

根据msdn,“在C#代码中,无法调用或覆盖Object.Finalize”。实际上,他们建议使用Dispose方法,因为它更易于控制。

答案 7 :(得分:0)

.NET中的终结器还有一个复杂的问题。如果类具有终结器并且没有得到Dispose()'d,或者Dispose()没有抑制终结器,则垃圾收集器将推迟收集,直到压缩第2代内存(上一代),因此对象是“排序” “但不是内存泄漏。 (是的,它最终会得到清理,但很可能直到申请终止。)

正如其他人所提到的,如果一个对象拥有非托管资源,它应该实现IDisposable模式。开发人员应该知道,如果一个对象实现了IDisposable,那么应该始终调用它的Dispose()方法。 C#提供了一种使用using语句自动执行此操作的方法:

using (myDataContext myDC = new myDataContext())
{
    // code using the data context here
}

using块在块退出时自动调用Dispose(),甚至通过抛出返回或异常退出。 using语句仅适用于实现IDisposable的对象。

并注意另一个混乱点; Dispose()是一个对象释放资源的机会,但实际上释放Dispose()'d对象。当没有对它们的活动引用时,.NET对象可以进行垃圾收集。从技术上讲,从AppDomain开始,任何对象引用链都无法访问它们。

答案 8 :(得分:0)

C ++中destructor()的等价物是Java中的finalizer()

当对象的生命周期即将结束时调用它们。