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

问题描述

我们有一个Java API,它是C API的包装。 这样,我们最终得到了几个java类,它们是C ++类的包装器。

这些类实现finalize方法,以释放已为其分配的内存。

通常,这很好。但是,在高负载情况下,我们会遇到内存不足的异常。 内存转储表明实际上所有内存(在这种情况下约为6Gb)都已填充有终结器队列和等待终结的对象。

为进行比较,C API自身的内存使用量永远不会超过150 Mb。

在低负载下,Java实现可以无限期运行。因此,这似乎并不是内存泄漏。看来,在高负载下,需要终结处理的新对象的生成速度要比终结处理器的执行速度更快。

很显然,“正确”的解决方法是减少所创建对象的数量。但是,这是一项艰巨的任务,需要一段时间。同时,是否有一种机制可以缓解此问题?例如,通过为GC提供更多资源。

解决方法

Java的设计思想是终结器可以用作超出范围的对象的主要清除机制。当对象的总数足够小以至于“始终扫描所有内容”垃圾收集器的开销是可以接受的时,这种方法可能几乎是可行的,但是在少数情况下,最终确定将是系统中适当的清除措施使用分代垃圾收集器(几乎所有JVM实现都将具有该垃圾收集器,因为与始终扫描所有内容相比,它提供了巨大的速度提升)。

在可行的情况下,将Closable与try-with-resources构造一起使用是一种非常优越的方法。无法保证finalize方法会在任何程度的及时性下被调用,并且在许多情况下,相互关联的对象的模式可能根本阻止它们被调用。尽管finalize可以用于某些目的,例如识别在持有资源的同时被不当抛弃的对象,但将其用作适当工具的目的相对较少。

如果确实需要使用终结器,则应该了解一个重要原则:与流行的看法相反,终结器不会在对象实际被垃圾回收时触发” –它们将在对象被回收时触发但是存在某个终结器(包括但不限于该对象自己的终结器),而在任何局部变量和任何其他对象中都存在对它的任何引用时,实际上无法对其进行垃圾回收此外,为避免必须在每个垃圾收集周期中检查所有对象,已使用了一段时间的对象将获得“自由通过”的权限,以便在任何垃圾回收周期检查所有对象。大多数GC周期。因此,如果带有终结器的对象在被丢弃之前还存活了一段时间,则它的终结器可能需要花费相当长的一段时间才能运行,并且它将保留其引用的对象足够长的时间,以至于它们可能还可以获得“免费通行证”。>

因此,我建议即使在有必要使用终结器的情况下,也应将它们的使用范围限制为私有对象,从而避免持有对清理任务中未明确需要的任何内容的强引用。

,

虚拟引用是Java中可用的终结器的替代方法。

虚拟引用使您可以更好地控制资源回收过程。

  • 您可以将显式资源处置(例如尝试使用资源构造)与GC基础处置相结合
  • 您可以使用多个线程进行事后管理

使用幻象引用非常困难。在this article中,您可以找到虚拟引用基础资源内务处理的最小示例。

在现代Java中,也有Cleaner类,它也基于幻像引用,但提供了易于使用的基础结构(引用队列,工作线程等)。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...