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

发布于 2024-08-13 18:06:21 字数 328 浏览 5 评论 0原文

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

  • 不保证运行(在java中)
  • 如果它们确实运行,它们可能会在相关对象成为最终确定的候选对象后运行任意时间
  • 并且(至少在java中),它们会产生惊人的巨大性能甚至坚持上课。

那么为什么要添加它们呢?我问了一位朋友,他含糊地说“你希望有一切可能的机会清理数据库连接之类的东西”,但在我看来这是一种不好的做法。为什么您应该依赖具有上述属性的东西来处理任何事情,即使是作为最后一道防线?尤其是,如果任何 API 中都设计了类似的东西,那么该 API 就会被嘲笑为不存在。

I'm not quite understanding why there are finalizers in languages such as java and c#. AFAIK, they:

  • are not guaranteed to run (in java)
  • if they do run, they may run an arbitrary amount of time after the object in question becomes a candidate for finalization
  • and (at least in java), they incur an amazingly huge performance hit to even stick on a class.

So why were they added at all? I asked a friend, and he mumbled something about "you want to have every possible chance to clean up things like DB connections", but this strikes me as a bad practice. Why should you rely on something with the above described properties for anything, even as a last line of defense? Especially when, if something similar was designed into any API, said API would get laughed out of existence.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

笑梦风尘 2024-08-20 18:06:21

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

在 .NET CLR 中,例如:

  • 不保证运行

如果程序没有被终止,终结器最终将始终运行。只是不确定何时运行。

  • 如果它们确实运行,它们可能会在相关对象成为最终确定的候选对象后运行任意时间

这是事实,但是它们仍然运行。

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

确切地知道何时释放您的(本机)资源并不总是那么容易 - 通过实现终结器,您可以保证它们将被正确清理,即使您的类以不太完美的方式使用。

  • 并且(至少在 Java 中),即使坚持在一个类上,它们也会造成惊人的巨大性能损失

同样,.NET CLR 的 GC 在这里具有优势。如果您实现了正确的接口 (IDisposable),并且开发人员正确实现了它,您就可以防止发生昂贵的终结部分。完成此操作的方法是,执行清理的用户定义方法可以调用 GC.SuppressFinalize,绕过终结器。

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

Well, they are incredibly useful, in certain situations.

In the .NET CLR, for example:

  • are not guaranteed to run

The finalizer will always, eventually, run, if the program isn't killed. It's just not deterministic as to when it will run.

  • if they do run, they may run an arbitrary amount of time after the object in question becomes a candidate for finalization

This is true, however, they still run.

In .NET, this is very, very useful. It's quite common in .NET to wrap native, non-.NET resources into a .NET class. By implementing a finalizer, you can guarantee that the native resources are cleaned up correctly. Without this, the user would be forced to call a method to perform the cleanup, which dramatically reduces the effectiveness of the garbage collector.

It's not always easy to know exactly when to release your (native) resources- by implementing a finalizer, you can guarantee that they will get cleaned up correctly, even if your class is used in a less-than-perfect manner.

  • and (at least in java), they incur an amazingly huge performance hit to even stick on a class

Again, the .NET CLR's GC has an advantage here. If you implement the proper interface (IDisposable), AND if the developer implements it correctly, you can prevent the expensive portion of finalization from occuring. The way this is done is that the user-defined method to do the cleanup can call GC.SuppressFinalize, which bypasses the finalizer.

This gives you the best of both worlds - you can implement a finalizer, and IDisposable. If your user disposes of your object correctly, the finalizer has no impact. If they don't, the finalizer (eventually) runs and cleans up your unmanaged resources, but you run into a (small) performance loss as it runs.

小梨窩很甜 2024-08-20 18:06:21

嗯,你在这里画的图画有点太乐观了。也不保证终结器能够在 .NET 中运行。典型的事故是终结器抛出异常或终结器线程超时(2 秒)。

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

回到你原来的问题,终结器对于释放内存以外的操作系统资源是必要的。垃圾收集器很好地管理内存,但不会执行任何操作来释放笔、画笔、文件、套接字、窗口、管道等。当对象使用此类资源时,必须确保在使用完后释放该资源它。终结器确保这种情况发生,即使程序忘记这样做。你几乎从来不会自己编写带有终结器的类,操作资源是由框架中的类包装的。

.NET 框架还有一种编程模式来确保尽早释放此类资源,以便该资源不会在终结器运行之前徘徊。所有具有终结器的类还实现 IDisposable.Dispose() 方法,允许您的代码显式释放资源。 .NET 程序员经常忘记这一点,但这通常不会导致问题,因为终结器确保它最终会完成。许多 .NET 程序员彻夜难眠,担心所有 Dispose() 调用是否都得到处理,并且论坛上已启动大量与此相关的主题。爪哇人一定是比较幸福的一群人。


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

其次,终结器线程失败是严重的程序失败。类似于出现内存不足异常或被电源线绊倒并拔掉机器插头。除了修复代码中的错误或重新布线之外,您对此无能为力。对于 Microsoft 来说,设计关键的终结器非常重要,他们不能依赖所有为 SQL Server 编写 .NET 代码的程序员来获得正确的代码。如果您自己弄乱了终结器,则不承担此类责任,接到客户电话的将是您,而不是 Microsoft。

Hmya, you are getting a picture painted here that's a bit too rosy. Finalizers are not guaranteed to run in .NET either. Typical mishaps are a finalizer that throws an exception or a time-out on the finalizer thread (2 seconds).

That was a problem when Microsoft decided to provide .NET hosting support in SQL Server. The kind of application where restarting the app to solve resource leaks isn't considered a viable workaround. .NET 2.0 acquired critical finalizers, enabled by deriving from the CriticalFinalizerObject class. The finalizer of such a class must adhere to the rulez of constrained execution regions (CERs), essentially a region of code where exceptions are suppressed. The kind of things you can do in a CER are very limited.

Back to your original question, finalizers are necessary to release operating system resources other than memory. The garbage collector manages memory very well but doesn't do anything to release pens, brushes, files, sockets, windows, pipes, etc. When an object uses such a resource, it must make sure to release the resource after it is done with it. Finalizers ensure that happens, even when the program forgot to do so. You almost never write a class with a finalizer yourself, operating resources are wrapped by classes in the framework.

The .NET framework also has a programming pattern to ensure such a resource is released early so the resource doesn't linger around until the finalizer runs. All classes that have finalizers also implement the IDisposable.Dispose() method, allowing your code to release a resource explicitly. This is often forgotten by a .NET programmer but that doesn't typically cause problems because the finalizer ensures it will eventually be done. Many .NET programmers have lost hours of sleep worrying whether or not all Dispose() calls are taken care of and massive numbers of threads have been started about it on forums. Java folks must be a happier lot.


Following up on your comment: exceptions and timeouts in the finalizer thread is something that you don't have to worry about. Firstly, if you find yourself writing a finalizer, take a deep breath and ask yourself if you're on the Right Path. Finalizers are for framework classes, you should be using such a class to use an operating resource, you'll get the finalizer built into that class for free. All the way down to the SafeHandle classes, they have a critical finalizer.

Secondly, finalizer thread failures are gross program failures. Similar to getting an OutOfMemory exception or tripping over the power cord and unplugging the machine. There isn't anything you can do about them, other than fixing the bug in your code or re-route the cable. It was important for Microsoft to design critical finalizers, they can't rely on all programmers that write .NET code for SQL Server to get that code right. If you fumble a finalizer yourself then there is no such liability, it will be you that gets the call from the customer, not Microsoft.

计㈡愣 2024-08-20 18:06:21

在 Java 中,终结器的存在是为了允许清理外部资源(存在于 JVM 外部并且当“父”Java 对象存在时无法进行垃圾收集的资源)。这一直是很少见的。例如,如果您正在与某些自定义硬件进行交互。

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

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

大多数 Java 代码不调用终结器。

In java finalizers exist to allow for the clean up of external resources (things that exist outside of the JVM and can't be garbage collected when the 'parent' java object is). This has always been rare. On example might be if you are interfacing with some custom hardware.

I think the reason that finalizers in java aren't guaranteed to run is that they might not have a chance to do so at program termination.

One thing you might do with a finalizer in 'pure' java is use it to test termination conditions- for example to check that all connections are closed and report an error if they are not. You aren't guaranteed that the error will be always caught but it will likely be caught at least some of the time which is enough to reveal a bug.

Most java code has no call for finalizers.

夜吻♂芭芘 2024-08-20 18:06:21

如果您阅读有关 Finalize() 的 JavaDoc,它会说它是“当垃圾收集确定不再有对该对象的引用时,由对象上的垃圾收集器调用。子类会重写 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();
        }
    }
 }

If you read the JavaDoc for finalize() it says it is "Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup."

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

So that's the "why". I guess you can argue whether their implementation is effective.

The best use I've found for finalize() is to detect bugs with freeing pooled resources. Most leaked objects will get garbage collected eventually and you can generate debug information.

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();
        }
    }
 }
冷…雨湿花 2024-08-20 18:06:21

它们的目的是释放本机资源(例如套接字、打开的文件、设备),这些资源在对对象的所有引用都被破坏之前无法释放,这是特定调用者会做的事情(一般来说)无从得知。另一种选择是微妙的、无法追踪的资源泄漏...

当然,在许多情况下,作为应用程序作者知道只有一个对数据库连接的引用(例如) ;在这种情况下,终结器不能替代当您知道已完成它时正确关闭它。

They're meant for freeing up native resources (e.g. sockets, open files, devices) that can't be released until all references to the object have been broken, which is something that a particular caller would (in general) have no way of knowing. The alternative would be subtle, impossible-to-trace resource leaks...

Of course, in many cases as the application author you'll know that there's only one reference to the DB connection (for example); in which case finalizers are no substitute for closing it properly when you know you're finished with it.

不打扰别人 2024-08-20 18:06:21

在 .Net 领域,无法保证它们何时运行。但他们会跑。

In .Net land, t is not guaranteed when they run. But they will run.

遥远的绿洲 2024-08-20 18:06:21

您指的是 Object.Finalize 吗?

根据 msdn 的说法,“在 C# 代码中,Object.Finalize 不能被调用或覆盖”。事实上,他们建议使用Dispose方法,因为它更可控。

Are you refering to Object.Finalize?

According to msdn, "In C# code, Object.Finalize cannot be called or overridden". In fact, they recommend using the Dispose method because it is more controllable.

烟─花易冷 2024-08-20 18:06:21

.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() 后的对象。当没有对 .NET 对象的活动引用时,它们适合进行垃圾回收。从技术上讲,从 AppDomain 开始,任何对象引用链都无法访问它们。

There's an additional complication with finalizers in .NET. If the class has a finalizer and does not get Dispose()'d, or Dispose() does not suppress the finalizer, the garbage collector will defer collecting until after compacting generation 2 memory (the last generation), so the object is "sort of" but not quite a memory leak. (Yes, it will get cleaned up eventually, but quite possibly not until application termination.)

As others have mentioned, if an object holds non-managed resources, it should implement the IDisposable pattern. Developers should be aware that if an object implements IDisposable, then it's Dispose() method should always be called. C# provides a way to automate this with the using statement:

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

The using block automatically calls Dispose() on block exit, even exits by return or exceptions being thrown. The using statement only works with objects that implement IDisposable.

And beware another confusion point; Dispose() is an opportunity for an object to release resources, but it does not actually release the Dispose()'d object. .NET objects are elligible for garbage collection when there are no active references to them. Technically, they can't be reached by any chain of object references, starting from the AppDomain.

椵侞 2024-08-20 18:06:21

C++ 中的destructor() 相当于Java 中的finalizer()

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

The equivalent ofdestructor() in C++ is finalizer() in Java.

They are invoked when the life cycle of an object is about to end.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文