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

凭借C ++和C#经验以及一些小知识,我现在正在开始一个Java + JNI(C ++)项目(Android,如果这很重要)。

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

在C#中,我可能会在一些托管包装类中包装本机IntPtr句柄。 并且当托管包装器被垃圾收集时,覆盖它的终结器来调用本机对象的析构函数。 SafeHandle,AddMemoryPressure等也可能对此有所帮助。

这是与Java最终确定的不同的故事。 你在Java中的’Hello world’之后知道的第二件事是使用finalize是不好的。 有没有其他方法可以在Java中实现这一目标? 也许使用PhantomReference?

好吧,让我们考虑为什么最终确定和Co是有问题的:正如你所知,不能保证在VM关闭之前调用finalize,这意味着特殊的清理代码不一定会运行(我做错了,我不喜欢在清理时看到在finalize队列中运行的任何问题,但这就是它的原因。 这也是C#中完全相同的情况

现在你的对象只消耗内存,当虚拟机被破坏时,无论如何都会被操作系统清理掉,因此唯一有问题的情况对你来说无关紧要。 所以,是的,你确实可以使用这个变体,它可以完美地工作,但它可能不完全被认为是一个伟大的架构设计 – 并且只要你的C ++代码添加资源,操作系统无法正确处理清理你会遇到问题

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

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

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

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

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

那么我们如何使用幻像参考来实现它。

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