问题标签 [finalization]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
java - 对于本机对等对象生命周期管理,是否真的应该避免使用 Java 终结器?
根据我作为 C++/Java/Android 开发人员的经验,我了解到终结器几乎总是一个坏主意,唯一的例外是管理 Java 调用 C/C++ 代码所需的“本机对等”对象通过 JNI。
我知道JNI: Properly manage the life of a java object question,但是这个问题解决了无论如何都不使用终结器的原因,对于本机对等点也不使用。因此,这是对上述问题中答案的反驳的问题/讨论。
Joshua Bloch 在其Effective Java中明确将这种情况列为他关于不使用终结器的著名建议的例外:
终结器的第二个合法用途涉及具有本地对等点的对象。本机对等点是普通对象通过本机方法委托给其的本机对象。因为本地对等点不是普通对象,所以垃圾收集器不知道它,也无法在其 Java 对等点被回收时回收它。假设本地对等点没有关键资源,终结器是执行此任务的合适工具。如果本地对等点拥有必须立即终止的资源,则该类应具有显式终止方法,如上所述。终止方法应该执行释放关键资源所需的任何操作。终止方法可以是本机方法,也可以调用一个。
(另请参阅“为什么 Java 中包含最终方法?” stackexchange 上的问题)
然后我在 Google I/O '17 上观看了真正有趣的How to manage native memory in Android演讲,Hans Boehm 实际上主张反对使用终结器来管理 java 对象的本地对等点,并引用 Effective Java 作为参考。在快速提到为什么显式删除本地对等点或基于范围自动关闭可能不是一个可行的选择后,他建议java.lang.ref.PhantomReference
改用。
他提出了一些有趣的观点,但我并不完全相信。我将尝试浏览其中的一些并陈述我的疑问,希望有人可以进一步阐明它们。
从这个例子开始:
在 java 类持有对在终结器方法中删除的本机对等点的引用时,布洛赫列出了这种方法的缺点。
终结器可以以任意顺序运行
如果两个对象变得不可达,终结器实际上以任意顺序运行,这包括两个指向彼此的对象同时变得不可达的情况,它们可能以错误的顺序被终结,这意味着实际上要终结的第二个对象尝试访问已经完成的对象。[...] 因此,您可以获得悬空指针并查看已释放的 c++ 对象 [...]
作为一个例子:
好的,但是如果 myBinaryPoly 是纯 Java 对象,这难道不是真的吗?据我了解,问题来自在其所有者的终结器中对其可能已终结的对象进行操作。如果我们只使用一个对象的终结器来删除它自己的私有本地对等点而不做任何其他事情,我们应该没问题,对吧?
终结器可以在本地方法运行时被调用
根据 Java 规则,但目前不在 Android 上:
对象 x 的终结器可能在 x 的方法之一仍在运行并访问本机对象时被调用。
multiply()
显示编译成的伪代码来解释这一点:
这很可怕,实际上我很欣慰这不会发生在 android 上,因为我的理解是,this
在other
垃圾超出范围之前收集垃圾!this
考虑到调用该方法的对象和该方法的参数,这甚至更奇怪other
,因此它们都应该在调用该方法的范围内已经“活着”。
一个快速的解决方法是在this
and上调用一些虚拟方法other
(丑陋!),或者将它们传递给本机方法(然后我们可以在其中检索mNativeHandle
并对其进行操作)。等等......this
默认情况下已经是本机方法的参数之一!
怎么可能this
被垃圾收集?
终结器可能会延迟太久
“为了使其正常工作,如果您运行的应用程序分配了大量本机内存和相对较少的 java 内存,那么垃圾收集器实际上可能不会足够迅速地运行以实际调用终结器 [...] 所以你实际上可能必须偶尔调用 System.gc() 和 System.runFinalization(),这很棘手 [...]”</p>
如果本地对等点只能被它所绑定的单个 java 对象看到,那么这个事实对系统的其余部分不是透明的,因此 GC 应该只需要管理 Java 对象的生命周期,因为它是纯java一个?很明显,我在这里看不到一些东西。
终结器实际上可以延长 java 对象的生命周期
[...] 有时终结器实际上将 java 对象的生命周期延长到另一个垃圾收集周期,这意味着对于分代垃圾收集器,它们实际上可能导致它在老一代中存活,并且生命周期可能会大大延长,因为只是有一个终结器。
我承认我真的不明白这里的问题是什么以及它与拥有本地同行有何关系,我会进行一些研究并可能更新问题:)
综上所述
目前,我仍然认为使用一种 RAII 方法是在 java 对象的构造函数中创建本地对等点并在 finalize 方法中删除实际上并不危险,前提是:
- 本机对等点不持有任何关键资源(在这种情况下,应该有一个单独的方法来释放资源,本机对等点只能充当本机领域中的 java 对象“对应物”)
- 本机对等点不跨越线程或在其析构函数中执行奇怪的并发操作(谁愿意这样做?!?)
- 本机对等指针永远不会在 java 对象之外共享,只属于单个实例,并且只能在 java 对象的方法内部访问。在 Android 上,java 对象可以访问同一类的另一个实例的本地对等点,就在调用接受不同本地对等点的 jni 方法之前,或者更好的是,只是将 java 对象传递给本地方法本身
- java 对象的终结器只删除它自己的本地对等点,不做任何其他事情
是否应该添加任何其他限制,或者即使遵守所有限制,也确实无法确保终结器是安全的?
garbage-collection - 手动调用`gc()`会导致所有`finalizers`立即执行吗?
我有一些我怀疑正在泄漏内存的代码。由于代码使用ccall
并维护了保存在指针中的重要信息,这些信息应该由ccall
在 s 期间编辑的代码释放finalizer
。
在我的调试中,我正在调用gc()
. 而且我想知道这是否会立即触发所有finalizer
附加到已移出范围的对象的 s
答案应该只关注 julie 0.5+。
arrays - 当类型为数组时,Fortran 终结子例程不起作用
最近我发现一个严重的问题是在某些情况下finalization 功能不起作用。
描述:
我定义了一个类型,比如说TYPE(testtype):: T
,其中使用了许多指针和可分配数组。还有一个终结子程序,比如说final:: de
。这个函数 ( de
) 工作正常(我把 write 放进去检查)。但是,当我定义一个数组时 TYPE(testtype),allocatable::T(:)
,在我完成使用它后,函数de
不会在我调用deallocate(T)
。这里我放上测试代码。可以检查,subdo1()
运行良好(输出"work"
),而最终输入subdo2()
失败。
我尝试了 gfortran 和 ifort(ifort 版本 17.0.0)
factory - 创建受控类型将在返回时调用 finalize
我想通过以下方式创建一个用于创建和初始化受控类型(有点像工厂)的函数:
由于 finalize 将处理一些在受控类型中指向的对象,因此我最终在 Bar 中出现了悬空指针,并且不知何故这会立即使程序崩溃,因此我从未看到“检查 2”。
这可以通过使用 new Controlled_Type 并在 Create 函数中返回一个指针来轻松解决。但是,我喜欢拥有受控类型而不是指向它的指针的想法,因为当 Bar 超出范围时,将自动调用终结。如果 Bar 是一个指针,我必须手动处理它。
有没有办法正确地做到这一点而不会以悬空指针结束?我应该在调整过程中做一些魔术吗?
go - 终结器统计
runtime.SetFinalizer
有没有办法获得使用注册但尚未运行的终结器总数?
我们正在考虑struct
在我们的一些产品中添加带有注册终结器的 a 以释放使用 分配的内存malloc
,并且该对象可能具有相对较高的分配率。如果我们可以监控终结器的数量,以确保它们不会堆积并触发内存不足错误(就像其他垃圾收集器一样),那就太好了。
(我知道显式释放可以避免这个问题,但我们不能更改现有代码,它不会调用Close
函数或类似的东西。)
c - GValue 初始化/终结的必要性
我应该什么时候打电话g_value_init
/ g_value_reset
?
目前,我在所有情况下都在使用g_value_init
和g_value_reset
,但我想知道它是否可以加速。
我至少知道:
- 当使用对象或盒装类型时,肯定需要调用
g_value_reset
,因为 GValue 可能已经获得了引用,或者如果它是GBoxed
. - 当使用像
guint
or这样的基本类型gboolean
(没有任何内存管理)时,g_value_reset
理论上应该不需要调用,因为不应该为它们分配内存。我什至已经阅读了实现,它被证明是正确的。但是,我担心作者可能会引入更改并开始分配一些内存(或只是进行一些跟踪),然后会导致内存泄漏。
这就是我目前的所有研究。我想扩展它,可能得到官方文档参考的支持。提前致谢。
java - Java 9 Cleaner 是否应该优于最终化?
在 Java 中,重写该finalize
方法会得到不好的评价,尽管我不明白为什么。像这样的类FileInputStream
使用它来确保close
在 Java 8 和 Java 10 中被调用。然而,Java 9 引入了java.lang.ref.Cleaner
它使用 PhantomReference 机制而不是 GC 终结。起初,我认为这只是为第三方类添加终结的一种方式。但是,在其 javadoc中给出的示例显示了一个可以使用终结器轻松重写的用例。
我应该finalize
根据 Cleaner 重写我的所有方法吗?(当然,我没有很多。只是一些使用操作系统资源的类,特别是用于 CUDA 互操作。)
据我所知,Cleaner(通过 PhantomReference)避免了finalizer
. 特别是,您无权访问已清理的对象,因此您无法复活它或它的任何字段。
但是,这是我能看到的唯一优势。清洁剂也很重要。事实上,它和 finalization 都使用了ReferenceQueue
! (难道你不喜欢阅读 JDK 是多么容易吗?)它比定稿更快吗?它是否避免等待两个 GC?如果许多对象排队等待清理,它会避免堆耗尽吗?(在我看来,所有这些的答案是否定的。)
最后,实际上没有什么可以保证阻止您在清理操作中引用目标对象。请仔细阅读长 API 说明!如果你最终引用了该对象,整个机制将默默地中断,不像最终化总是试图跛行。最后,虽然终结线程由 JVM 管理,但创建和持有 Cleaner 线程是您自己的责任。
java - 为什么 Java 9 中不推荐使用 finalize() 方法?
(这个问题不同于Why would you ever implement finalize()?这个问题是关于 Java 平台的弃用,另一个问题是关于是否应该在应用程序中使用这种机制。)
为什么finalize()
Java 9 中不推荐使用该方法?
是的,它可能以错误的方式使用(比如从垃圾收集中保存一个对象[尽管只有一次],或者尝试关闭其中的一些本机资源[虽然总比不关闭要好])以及许多其他方法可能会被错误地使用。
那么是否finalize()
真的如此危险或绝对无用以至于有必要将其踢出Java?