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

发布于 2024-11-23 17:31:54 字数 569 浏览 1 评论 0原文

凭借 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?

With C++ and C# experience and some little Java knowledge I'm now starting a Java+JNI (C++) project (Android, if that matters).

I have a native method, that creates some C++ class and returns a pointer to it as a Java long value (say, handle). And then other native methods called from Java code here and there, use the handle as a parameter to do some native operations on this class. C++ side does not own the object, it's Java side who does. But in the current architecture design it's hard to define who exactly owns the object and when to delete it. So it would probably be nice to make Java VM garbage collector to manage the object's lifetime somehow. The C++ class does not consume any resources, except some piece of memory, not large. So it's OK, if several such objects will not be destructed.

In C# I would probably wrap the native IntPtr handle in some managed wrapper class. And override it's finalizer to call native object's destructor when the managed wrapper is garbage collected. SafeHandle, AddMemoryPressure, etc. might be also of help here.

This is a different story with Java's finalize. The second thing you know after 'Hello world' in Java, is that using finalize is bad. Are there any other ways to accomplish this in Java? Maybe using PhantomReference?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

戈亓 2024-11-30 17:31:54

好吧,让我们考虑一下为什么 Finalize 和 Co 有问题的原因:如您所知,不能保证在 VM 关闭之前调用 Finalize,这意味着特殊的清理代码不一定会运行(在我看来这是一个错误的决定,我不认为在清理时没有看到任何运行最终队列的问题,但事实就是这样)。这也与 C# 中的情况完全相同。

现在,您的对象仅消耗内存,当 VM 被销毁时,操作系统将清除这些内存,因此 Finalize 出现问题的唯一情况对您来说并不重要。所以,是的,你确实可以使用这个变体,它会工作得很好,但它可能不完全被认为是一个伟大的架构设计 - 一旦你向你的 C++ 代码添加资源,而操作系统无法正确处理清理,你会遇到问题

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

Well let's consider the reason WHY finalize and Co are problematic: As you know there's no guarantee that the finalize will be called before the VM is shut down, which means that special cleanup code won't necessarily run (imo a bad decision, I don't see any problems to run through the finalize queue at cleanup, but well that's how it is). Also this is exactly the same situation in C#

Now your objects only consume memory, which will be cleaned up by the OS anyhow when the VM is destroyed, so the only case where finalize is problematic won't matter for you. So yes you can indeed use this variant and it'll work perfectly fine, but it may not exactly be considered a great architectural design - and as soon as you add resources to your C++ code where the OS doesn't handle the cleanup correctly you will run into problems

Also note that implementing a finalizer results in some additional overhead for the GC and means it takes two cycles to cleanup one of these objects (and whatever you do, don't ever save an object in the finalize method)

野心澎湃 2024-11-30 17:31:54

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

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

If you understand why you should avoid using Java's finalize method, you will also understand how to use it correctly. Using finalize for closing system resources (files and handles) is bad because you don't actually know when those resources will be closed and released. Using complex finalize logic is bad as your object reference can leak out and get pinned in memory again.

For your scenario, it is perfectly fine to use finalize.

不知在何时 2024-11-30 17:31:54

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

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

using a wrapper with a finalizer is a decent solution here

but if you really don't wanna do that you can use a PhantomReference with a ReferenceQueue to clean it up (but you are going to require a separate thread to poll the queue)

哭泣的笑容 2024-11-30 17:31:54

那么我们如何使用幻像引用来实现呢?

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

So how can we achieve it using phantom reference.

  1. Create a wrapper object for your native intPtr object. Create a
    phantom reference(with a reference queue) on the wrapper object.
  2. Create and maintain a map of phantom reference to intPtr.
  3. Create a thread that will be monitoring the reference queue for finalized
    wrapper object instances.
  4. This thread will get the phantom reference from reference queue, lookup intPtr using phantom reference and call destructor on native int object referenced by intPtr.
  5. While all this happening, you can go about happily using the
    wrapper object in your java code.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文