本机对等对象生命周期管理是否也应避免使用Java finalizer?

本机对等对象生命周期管理是否也应避免使用Java finalizer?,java,android,java-native-interface,googleio,Java,Android,Java Native Interface,Googleio,根据我作为C++/Java/Android开发人员的经验,我逐渐了解到终结器几乎总是一个坏主意,唯一的例外是Java one需要管理一个“本地对等”对象,以便通过JNI调用C/C++代码 我知道这一点,但这个问题说明了无论如何都不使用终结器的原因,本地对等方也不使用终结器。因此,这是一个关于上述问题答案混乱的问题/讨论 Joshua Bloch在他的著作中明确地将此案例列为他关于不使用终结器的著名建议的例外: 终结器的第二个合法使用涉及具有本机对等的对象。本机对等对象是一个本机对象,普通对象通过

根据我作为C++/Java/Android开发人员的经验,我逐渐了解到终结器几乎总是一个坏主意,唯一的例外是Java one需要管理一个“本地对等”对象,以便通过JNI调用C/C++代码

我知道这一点,但这个问题说明了无论如何都不使用终结器的原因,本地对等方也不使用终结器。因此,这是一个关于上述问题答案混乱的问题/讨论

Joshua Bloch在他的著作中明确地将此案例列为他关于不使用终结器的著名建议的例外:

终结器的第二个合法使用涉及具有本机对等的对象。本机对等对象是一个本机对象,普通对象通过本机方法委托给它。因为本机对等体不是普通对象,所以垃圾收集器不知道它,并且在回收其Java对等体时无法回收它。终结器是执行此任务的合适工具,前提是本机对等方不持有任何关键资源。如果本机对等方持有必须立即终止的资源,则该类应具有显式终止方法,如上所述。终止方法应该执行释放关键资源所需的任何操作。终止方法可以是本机方法,也可以调用本机方法

(另见有关stackexchange的问题)

然后,我在Google I/O'17上观看了一场非常有趣的演讲,Hans Boehm实际上主张反对使用终结器来管理java对象的本机对等体,并引用了有效的java作为参考。在快速提到为何显式删除本地对等点或基于作用域的自动关闭可能不是一个可行的选择后,他建议使用
java.lang.ref.PhantomReference

他提出了一些有趣的观点,但我并不完全相信。我将试着浏览其中的一些,并陈述我的疑问,希望有人能进一步阐明它们

从这个例子开始:

类二进制多边形{
长的MNATIVE句柄;/ /持有C++原始指针
私有二进制多边形(长nativeHandle){
mNativeHandle=nativeHandle;
}
私有静态本机long nativeMultiply(长xcpptr、长yCppPtr);
二进制多边形乘法(二进制多边形其他){
返回新的二进制多边形(nativeMultiply(mNativeHandle,other.mNativeHandler));
}
// …
静态本征空洞本征元素(长cppPtr);
受保护的void finalize(){
nativeDelete(mNativeHandle);
}
}
当java类持有对在finalizer方法中被删除的本机对等方的引用时,Bloch列出了这种方法的缺点

终结器可以按任意顺序运行

如果两个对象变得不可访问,终结器实际上会以任意顺序运行,这包括两个相互指向的对象在以错误顺序终结的同时变得不可访问,这意味着要终结的第二个对象实际上会尝试访问已经终结的对象。(…)因此,您可以得到悬空指针,并查看已释放的C++对象[…] /P> 例如:

class-SomeClass{
二元聚合mMyBinaryPoly:
…
//绝对不要对当前的BinaryPoly执行此操作!
受保护的void finalize(){
Log.v(“BPC”,“drop+…+myBinaryPoly.toString());
}
}
好的,但如果myBinaryPoly是一个纯Java对象,这难道不是真的吗?据我所知,问题来自于在其所有者的终结器中对可能终结的对象进行操作。如果我们只使用对象的终结器删除其自己的私有本机对等对象,而不做任何其他事情,我们应该没事,对吗

在本机方法运行时可以调用终结器

根据Java规则,但目前不在Android上:
当x的一个方法仍在运行并访问本机对象时,可以调用对象x的终结器

显示了编译到的
multiply()
的伪代码来解释这一点:

BinaryPoly乘法(BinaryPoly其他){
long tmpx=this.mNativeHandle;//最后一次使用“this”
long tmpy=other.mNativeHandle;//上次使用other
BinaryPoly结果=新的BinaryPoly();
//GC发生在这里。“这个”和“其他”可以回收并最终确定。
//仍然需要tmpx和tmpy。但是终结器可以在这里删除tmpx和tmpy!
result.mNativeHandle=nativeMultiply(tmpx,tmpy)
返回结果;
}
这太可怕了,我真的松了一口气,因为我知道
This
other
会在超出范围之前收集垃圾!考虑到
This
是调用该方法的对象,而
other
是方法,因此它们都应该在调用该方法的范围内“处于活动状态”

一个快速的解决方法是在
this
other
(丑陋!)上调用一些伪方法,或者将它们传递给本机方法(在那里我们可以检索
mNativeHandle
并对其进行操作)。然后等待……
本机方法的默认参数之一

JNIEXPORT void JNICALL Java_package_BinaryPoly_multiply
(JNIEnv* env, jobject thiz, jlong xPtr, jlong yPtr) {}
这个
怎么可能被垃圾收集

终结器可能延迟太长时间

“为了使其正常工作,如果您运行的应用程序分配了大量的本机内存和相对较少的java内存,那么垃圾回收器实际上可能不会足够快地运行以实际调用终结器[…],因此您实际上可能需要调用System.gc()和System.runFinalization()偶尔,这是很难做到的。