是否可以让 GC 管理本机对象的生命周期?



有了c++和c#的经验和一些Java知识,我现在开始一个Java+JNI (c++)项目(Android,如果重要的话)。

我有一个本地方法,它创建一些c++类,并返回一个指向它的指针作为Java长值(比如句柄)。然后其他从Java代码中调用的本地方法,使用句柄作为参数在这个类上做一些本地操作。c++端不拥有对象,是Java端拥有对象。但是在当前的架构设计中,很难定义谁确切拥有对象以及何时删除它。因此,使用Java VM垃圾收集器以某种方式管理对象的生命周期可能会很好。c++类不消耗任何资源,除了一些不大的内存。所以,如果几个这样的对象不会被破坏,这是可以的。

在c#中,我可能会将本机IntPtr句柄包装在一些托管包装器类中。当托管包装器被垃圾收集时,重写它的终结器来调用本机对象的析构函数。SafeHandle, AddMemoryPressure等在这里可能也有帮助。

对于Java的finalize,这是一个不同的故事。在Java中" Hello world "之后你知道的第二件事是,使用finalize是不好的。在Java中还有其他的方法来实现这个吗?也许可以使用PhantomReference?

让我们考虑一下finalize和Co有问题的原因:正如你所知道的,不能保证在VM关闭之前调用finalize,这意味着特殊的清理代码不一定会运行(在我看来,这是一个糟糕的决定,我认为在清理时运行finalize队列没有任何问题,但这就是问题所在)。在c#

中也是同样的情况

现在你的对象只消耗内存,当VM被销毁时,这些内存将被操作系统清理,所以finalize是有问题的唯一情况对你来说并不重要。所以,是的,你确实可以使用这种变体,它会工作得很好,但它可能不被认为是一个伟大的架构设计——一旦你在c++代码中添加资源,而操作系统不能正确处理清理,你就会遇到问题

还要注意,实现终结器会给GC带来一些额外的开销,并且意味着需要两个周期来清理这些对象中的一个(无论您做什么,都不要在finalize方法中保存对象)

如果您了解为什么应该避免使用Java的finalize方法,您也将了解如何正确使用它。使用finalize关闭系统资源(文件和句柄)是不好的,因为您实际上不知道这些资源将在何时关闭和释放。使用复杂的finalize逻辑是不好的,因为您的对象引用可能泄漏并再次固定在内存中。

对于您的场景,使用finalize完全可以。

在这里使用带有终结器的包装器是一个不错的解决方案

但是如果你真的不想这样做,你可以使用一个带ReferenceQueue的PhantomReference来清理它(但是你将需要一个单独的线程来轮询队列)

那么如何使用幻影引用来实现呢?

  1. 为本机intPtr对象创建包装器对象。创建一个包装器对象上的虚引用(带有引用队列)。
  2. 创建并维护intPtr的虚参映射。
  3. 创建一个线程,该线程将监视引用队列完成
  4. 这个线程将从引用队列中获取虚引用,使用虚引用查找intPtr,并在intPtr引用的本机int对象上调用析构函数。
  5. 当所有这些发生时,您可以愉快地使用

最新更新