在 Java 中为 JOGL 释放直接缓冲区本机内存
我使用直接缓冲区 (java.nio) 来存储 JOGL 的顶点信息。这些缓冲区很大,并且在应用寿命期间会多次更换。内存没有及时释放,经过几次替换后我的内存耗尽了。
似乎没有好的方法可以使用 java.nio 的缓冲区类来解除分配。我的问题是:
JOGL 中是否有某种方法可以删除直接缓冲区?我正在研究 glDeleteBuffer(),但看起来这只会从显卡内存中删除缓冲区。
谢谢
I am using direct buffers (java.nio) to store vertex information for JOGL. These buffers are large, and they are replaced several times during the application life. The memory is not deallocated in time and I am running out of memory after a few replacements.
It seems that there is not good way to deallocate using java.nio's buffer classes. My question is this:
Is there some method in JOGL to delete Direct Buffers? I am looking into glDeleteBuffer(), but it seems like this only deletes the buffer from the video card memory.
Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
直接 NIO 缓冲区使用非托管内存。这意味着它们分配在本机堆上,而不是 Java 堆上。因此,只有当 JVM 耗尽 Java 堆上的内存(而不是本机堆上的内存)时,才会释放它们。换句话说,它是非托管的 = 由您来管理它们。不鼓励强制垃圾收集,并且大多数情况下并不能解决此问题。
当您知道直接 NIO 缓冲区对您来说变得无用时,您必须使用其 sun.misc.Cleaner (StaxMan 是对的)来释放其本机内存并调用 clean() (Apache Harmony 除外),调用 free() (使用 Apache Harmony)或使用更好的公共 API 来做到这一点(也许在 Java > 12 中,扩展 AutoCloseable 的 AutoCleaning?)。
这不是 JOGL 的工作,您可以使用纯 Java 代码自己完成。 我的示例 遵循 GPL v2 且 此示例采用更宽松的许可证。
编辑: 我的最新的示例甚至可以使用 Java 1.9,并支持 OpenJDK、Oracle Java、Sun Java、Apache Harmony、GNU Classpath 和 Android。您可能需要删除一些语法糖才能使其与 Java 一起使用1.7(多重捕获、钻石和仿制药)。
参考:http://www.ibm.com/developerworks/library/j -nativememory-linux/
参考:http://docs.oracle。 com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
此解决方案集成在Java 14:
您可以通过调用 MemorySegment.ofByteBuffer(ByteBuffer),获取其内存地址并释放它(这是一个受限制的方法) Java 16:
请注意,在许多重要情况下,您仍然需要使用反射来找到可以释放的缓冲区,通常是当您的直接 NIO 缓冲区不是 ByteBuffer 时。
注意:sun.misc.Cleaner 已移动到 jdk.internal.ref.Cleaner 位于“java.base”模块中,后者实现了 java.lang. lang.Runnable (感谢 Alan Bateman 提醒我这种差异)很短一段时间,但 现在不再是这样了。你必须调用sun.misc.Unsafe.invokeCleaner(),它是 在 JogAmp 的 Gluegen 中完成。我更喜欢使用 Cleaner 作为 Runnable,因为它避免依赖 sun.misc.Unsafe,但它现在不起作用。
我的最后一个建议适用于 Java 9、10、11 和 12。
我的最新示例 需要使用孵化功能(需要 Java >= 14),但非常非常简单。
注意:自 Java 18 起,不再可能使用内存段来释放预先存在的直接 NIO 缓冲区。相反,请通过调用 MemorySegment.allocateNative() 创建本机内存段,调用 asByteBuffer() 并关闭当您不再需要它时,它的内存会话。它更具侵入性,但这一变化背后可能有出色的 API 设计决策。也许 Java >= 21 中的 MemorySegment.ofBuffer() 有帮助,但我担心它使用了一个您无法安全关闭的全局竞技场。
有 Lucene 中的一个很好的例子,具有更宽松的许可证。
The direct NIO buffers use unmanaged memory. It means that they are allocated on the native heap, not on the Java heap. As a consequence, they are freed only when the JVM runs out of memory on the Java heap, not on the native heap. In other terms, it's unmanaged = it's up to you to manage them. Forcing the garbage collection is discouraged and won't solve this problem most of the time.
When you know that a direct NIO buffer has become useless for you, you have to release its native memory by using its sun.misc.Cleaner (StaxMan is right) and call clean() (except with Apache Harmony), call free() (with Apache Harmony) or use a better public API to do that (maybe in Java > 12, AutoCleaning that extends AutoCloseable?).
It's not JOGL job to do that, you can use plain Java code to do it yourself. My example is under GPL v2 and this example is under a more permissive license.
Edit.: My latest example works even with Java 1.9 and supports OpenJDK, Oracle Java, Sun Java, Apache Harmony, GNU Classpath and Android. You might have to remove some syntactical sugar to make it work with Java < 1.7 (the multi catches, the diamonds and the generics).
Reference: http://www.ibm.com/developerworks/library/j-nativememory-linux/
Reference: http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
This solution is integrated in Java 14:
You can wrap a byte buffer into a memory segment by calling MemorySegment.ofByteBuffer(ByteBuffer), get its memory address and free it (this is a restricted method) in Java 16:
Note that you still need to use reflection in many non trivial cases in order to find the buffer that can be deallocated, typically when your direct NIO buffer isn't a ByteBuffer.
N.B: sun.misc.Cleaner has been moved into jdk.internal.ref.Cleaner in Java 1.9 in the module "java.base", the latter implemented java.lang.Runnable (thanks to Alan Bateman for reminding me that difference) for a short time but it's no longer the case. You have to call sun.misc.Unsafe.invokeCleaner(), it's done in JogAmp's Gluegen. I preferred using the Cleaner as a Runnable as it avoided to rely on sun.misc.Unsafe but it doesn't work now.
My last suggestion works with Java 9, 10, 11 and 12.
My very latest example requires the use of an incubated feature (requires Java >= 14) but is very very simple.
N.B: Using a memory segment to free a preexisting direct NIO buffer is no longer possible since Java 18. Instead, create a native memory segment by calling MemorySegment.allocateNative(), call asByteBuffer() on it and close its memory session when you no longer need it. It's a lot more invasive but there are probably excellent API design decisions behind this change. Maybe MemorySegment.ofBuffer() in Java >= 21 helps but I fear that it uses a global arena that you can't safely close.
There is a good example in Lucene under a more permissive license.
直接缓冲区很棘手,并且没有通常的垃圾收集保证 - 有关更多详细信息,请参阅:http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
如果您遇到问题,我建议分配一次并重新使用缓冲区,而不是重复分配和取消分配。
Direct buffers are tricky and don't have the usual garbage collection guarantees - see for more detail: http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
If you are having issues, I'd suggest allocating once and re-using the buffer rather than allocating and deallocating repeatedly.
释放的方式很糟糕——软引用基本上被插入到 Cleaner 对象中,然后当拥有的 ByteBuffer 被垃圾收集时,它就会释放。但这并不能真正保证及时调用。
The way in which deallocation is done is awful -- a soft reference is basically inserted into a Cleaner object, which then does deallocation when owning ByteBuffer is garbage collected. But this is not really guaranteed to be called in timely manner.
释放直接缓冲区是垃圾收集器在标记 ByteBuffer 对象一段时间后完成的工作。
您可以尝试在删除对缓冲区的最后一个引用后立即调用 gc。至少内存的释放速度有可能更快一些。
Deallocating a Direct Buffer is a job done by the garbage collector some time after the ByteBuffer object is marked.
You could try calling the gc immediatly after deleting the last reference to your buffer. At least there's a chance that the memory will be free'd a bit faster.
使用 gouessej 的回答中的信息,我能够将这个实用程序类放在一起,以释放给定 ByteBuffer 的直接内存分配。当然,这只能作为最后的手段,而不应该真正在生产代码中使用。
在 Java SE 版本 10.0.2 中测试并运行。
Using information in gouessej's answer, I was able to put this utility class together for freeing the direct memory allocation of a given ByteBuffer. This, of course, should only be used as a last resort, and shouldn't really be used in production code.
Tested and working in Java SE version 10.0.2.
您可以完全在受支持的公共 API 内简单地完成此操作,而不是滥用非公共 api 的反射。
编写一些用 NewDirectByteBuffer 包装 malloc 的 JNI(记住设置顺序),以及一个类似的函数来释放它。
Rather than abuse reflection of non-public apis you can do this trivially, entirely within the supported public ones.
Write some JNI which wraps malloc with NewDirectByteBuffer (remember to set the order), and an analogous function to free it.