在使用终结器以释放由C调用分配的内存的Java API中发生内存不足错误。

huangapple go评论64阅读模式
英文:

Out of memory errors in Java API that uses finalizers in order to free memory allocated by C calls

问题

我们有一个Java API,它是C API的包装。因此,我们最终会得到几个Java类,它们是C++类的包装。

这些类实现了finalize方法,以释放为它们分配的内存。

一般情况下,这样是有效的。然而,在高负载情况下,我们会遇到内存不足的异常。内存转储显示几乎所有内存(在这种情况下约为6Gb)都被用于finalizer队列和等待被finalize的对象。

相比之下,单独的C API永远不会超过约150Mb的内存使用量。

在低负载情况下,Java实现可以无限运行。因此,这似乎不是内存泄漏。看起来只是在高负载情况下,新创建需要finalizing的对象的速度比finalizer的执行速度更快。

显然,"正确"的修复方法是减少创建的对象数量。然而,这是一个重大的任务,需要一段时间。在此期间,是否有一种机制可以帮助缓解这个问题?例如,通过为垃圾收集器提供更多资源。

英文:

We have a Java API that is a wrapper around a C API.
As such, we end up with several Java classes that are wrappers around C++ classes.

These classes implement the finalize method in order to free the memory that has been allocated for them.

Generally, this works fine. However, in high-load scenarios we get out of memory exceptions.
Memory dumps indicate that virtually all the memory (around 6Gb in this case) is filled with the finalizer queue and the objects waiting to be finalized.

For comparison, the C API on its own never goes over around 150 Mb of memory usage.

Under low load, the Java implementation can run indefinitely. So this doesn't seem to be a memory leak as such. It just seem to be that under high load, new objects that require finalizing are generated faster than finalizers get executed.

Obviously, the 'correct' fix is to reduce the number of objects being created. However, that's a significant undertaking and will take a while. In the meantime, is there a mechanism that might help alleviate this issue? For example, by giving the GC more resources.

答案1

得分: 3

Java的设计理念围绕着finalizer(终结器),将其作为对象在超出范围时的主要清理机制。当对象总数较小,可以接受“始终扫描所有内容”的垃圾回收器开销时,这种方法可能几乎可行;但在存在分代垃圾回收器的系统中(几乎所有JVM实现都会有,因为它相对于始终扫描所有内容可以显著提高速度),只有少数情况下finalization(终结)才会是适当的清理措施。

每当可行时,结合try-with-resources结构使用Closable会是一种更优越的方法。无法保证finalize方法会在任何时间内被调用,而且在许多情况下,相互关联的对象模式可能会阻止它们被调用。虽然finalize在某些情况下可能有用,比如识别在占用资源的同时被错误地丢弃的对象,但是适用finalize的情况相对较少。

如果确实需要使用finalizer,您应该了解一个重要原则:与普遍观念相反,finalizer并不在实际回收对象时触发,而是在一个对象本应该被垃圾回收但因为某处存在finalizer而没有被回收时触发[包括但不限于对象自身的finalizer]。只要任何局部变量、任何其他对象中存在引用,或者任何带有尚未完成运行的finalizer的对象中存在引用,就无法实际回收任何对象。此外,为了避免在每个垃圾回收周期都必须检查所有对象,已经存活一段时间的对象在大多数GC周期中将获得“免检”权限。因此,如果带有finalizer的对象在被丢弃之前存活了一段时间,它的finalizer可能需要相当长的时间才能运行,并且它将使它所持有引用的对象保持足够长的时间,以便它们也有可能获得“免检”权限。

因此,我建议尽可能地,即使需要使用finalizer,也应将其限制在私有对象上,而这些对象本身又避免持有任何不明确需要进行清理的内容。

英文:

Java was designed around the idea that finalizers could be used as the primary cleanup mechanism for objects that go out of scope. Such an approach may have been almost workable when the total number of objects was small enough that the overhead of an "always scan everything" garbage collector would have been acceptable, but there are relatively few cases where finalization would be appropriate cleanup measure in a system with a generational garbage collector (which nearly all JVM implementations are going to have, because it offers a huge speed boost compared to always scanning everything).

Using Closable along with a try-with-resources constructs is a vastly superior approach whenever it's workable. There is no guarantee that finalize methods will get called with any degree of timeliness, and there are many situations where patterns of interrelated objects may prevent them from getting called at all. While finalize can be useful for some purposes, such as identifying objects which got improperly abandoned while holding resources, there are relatively few purposes for which it would be the proper tool.

If you do need to use finalizers, you should understand an important principle: contrary to popular belief, finalizers do not trigger when an object is actually garbage collected"--they fire when an object would have been garbage collected but for the existence of a finalizer somewhere [including, but not limited to, the object's own finalizer]. No object can actually be garbage collected while any reference to it exists in any local variable, in any other object to which any reference exists, or any object with a finalizer that hasn't run to completion. Further, to avoid having to examine all objects on every garbage-collection cycle, objects which have been alive for awhile will be given a "free pass" on most GC cycles. Thus, if an object with a finalizer is alive for awhile before it is abandoned, it may take quite awhile for its finalizer to run, and it will keep objects to which it holds references around long enough that they're likely to also earn a "free pass".

I would thus suggest that to the extent possible, even when it's necessary to use finalizer, you should limit their use to privately-held objects which in turn avoid holding strong references to anything which isn't explicitly needed for their cleanup task.

答案2

得分: 2

幻影引用是 Java 中可替代终结器的一种方法。

幻影引用允许您更好地控制资源回收过程。

  • 您可以将显式资源释放(例如使用资源管理的 try-with-resources 结构)与基于 GC 的释放结合使用
  • 您可以利用多个线程进行事后清理

然而,使用幻影引用是相对复杂的。在这篇文章中,您可以找到幻影引用基础资源清理的最简示例。

在现代 Java 中,还有基于幻影引用的 Cleaner 类,但它提供了基础设施(引用队列、工作线程等),以便更容易使用。

英文:

Phantom references is an alternative to finalizers available in Java.

Phantom references allow you to better control resource reclamation process.

  • you can combine explicit resource disposal (e.g. try with resources construct) with GC base disposal
  • you can employ multiple threads for postmortem housekeeping

Using phantom references is complicated tough. In this article you can find a minimal example of phantom reference base resource housekeeping.

In modern Java there are also Cleaner class which is based on phantom reference too, but provides infrastructure (reference queue, worker threads etc) for ease of use.

huangapple
  • 本文由 发表于 2020年10月8日 22:33:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/64264807.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定