我们有一个Java API,它是C API的包装器。因此,我们最终得到了几个作为C++类包装器的Java类。
这些类实现finalize方法,以便释放已分配给它们的内存。
一般来说,这样做效果很好。然而,在高负载的情况下,我们会遇到内存不足的异常。内存转储表示实际上所有内存(在本例中约为6 6Gb )都被终结器队列和等待完成的对象填满。
相比之下,C API本身的内存使用量永远不会超过150Mb左右。
在低负载下,Java实现可以无限运行。所以这看起来不像是内存泄漏。这似乎只是在高负载下,需要终结器的新对象的生成速度比终结器的执行速度更快。
显然,“正确的”修复方法是减少创建的对象数量。然而,这是一项重要的任务,需要一段时间。同时,有没有一种机制可以帮助缓解这个问题?例如,通过给GC更多的资源。
发布于 2020-10-08 23:42:41
Java的设计思想是终结器可以用作超出作用域的对象的主要清理机制。当对象的总数小到可以接受“始终扫描所有内容”垃圾收集器的开销时,这种方法可能几乎是可行的,但在具有分代垃圾收集器的系统中,完成是适当的清理措施的情况相对较少(几乎所有JVM实现都会有,因为与总是扫描所有内容相比,它提供了巨大的速度提升)。
只要可行,将Closable
与try- with -resources结构一起使用是一种非常优越的方法。不能保证finalize
方法将以任何程度的及时性被调用,并且在许多情况下,相关对象的模式可能根本阻止它们被调用。虽然finalize
可以用于某些目的,例如识别在持有资源时被不适当地丢弃的对象,但对于相对较少的目的来说,它将是适当的工具。
如果你确实需要使用终结器,你应该理解一个重要的原则:与流行的想法相反,终结器不会在对象实际被垃圾回收时触发--它们在对象本应被垃圾回收时触发,但在某个地方存在终结器,包括但不限于对象自己的终结器。任何对象都不能被垃圾回收,因为它的引用存在于任何局部变量中,存在于任何其他存在引用的对象中,或者存在于任何具有未运行到完成的终结器的对象中。此外,为了避免在每个垃圾收集周期中检查所有对象,在大多数GC周期中,将给予已存活一段时间的对象一个“自由通行证”。因此,如果具有终结器的对象在被丢弃之前存活了一段时间,那么它的终结器可能需要相当长的时间才能运行,并且它将保留其引用的对象足够长的时间,以便它们也可能获得“自由传递”。
因此,我建议,在可能的范围内,即使有必要使用finalizer,您也应该将它们的使用限制在私有对象上,这样就避免了对清理任务不显式需要的任何对象的强引用。
发布于 2020-10-09 06:42:18
幻影引用是Java中可用的终结器的替代方法。
Phantom引用允许您更好地控制资源回收过程。
使用幻影引用很复杂,很困难。在this article中,您可以找到一个基于虚拟引用的资源内务的最小示例。
在现代Java语言中,也有基于幻影引用的Cleaner类,但为了便于使用,它提供了基础设施(引用队列、工作线程等)。
https://stackoverflow.com/questions/64264807
复制相似问题