在 Java 中为 JOGL 解除分配直接缓冲区本机内存

2022-09-03 05:51:17

我正在使用直接缓冲区(java.nio)来存储JOGL的顶点信息。这些缓冲液很大,在应用生命周期内会更换多次。内存未及时解除分配,并且在几次替换后内存不足。

似乎没有使用java.nio的缓冲类进行释放的好方法。我的问题是这样的:

JOGL 中是否有某种方法可以删除直接缓冲区?我正在研究glDeleteBuffer(),但似乎这只会从视频卡内存中删除缓冲区。

谢谢


答案 1

直接 NIO 缓冲区使用非托管内存。这意味着它们被分配在本机堆上,而不是在Java堆上。因此,仅当 JVM 在 Java 堆上(而不是本机堆)上的内存不足时,它们才会被释放。换句话说,它是非托管的=由你来管理它们。不鼓励强制垃圾回收,并且在大多数情况下不会解决此问题。

当你知道一个直接的NIO缓冲区对你来说已经变得毫无用处时,你必须通过使用它的sun.misc.Cleaner(StaxMan是对的)来释放它的原生内存,并调用clean()(Apache Harmony除外),调用free()(使用Apache Harmony)或使用更好的公共API来做到这一点(也许在Java>12中,AutoCleaning扩展了AutoCloseable?)。

这不是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/

Direct ByteBuffer 对象会自动清理其本机缓冲区,但只能作为 Java 堆 GC 的一部分执行此操作,因此它们不会自动响应本机堆上的压力。仅当 Java 堆变得如此之满以至于无法为堆分配请求提供服务,或者 Java 应用程序显式请求它时(不建议这样做,因为它会导致性能问题),才会发生 GC。

参考资料: http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct

直接缓冲区的内容可能驻留在正常的垃圾回收堆之外

此解决方案集成在 Java 14 中:

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   ...
}

您可以通过调用MemorySegment.ofByteBuffer(ByteBuffer)将字节缓冲区包装到内存段中,获取其内存地址并释放它(这是一种受限制的方法):在Java 16中:

CLinker.getInstance().freeMemoryRestricted(MemorySegment.ofByteBuffer(myByteBuffer).address());

请注意,在许多非平凡的情况下,您仍然需要使用反射才能找到可以释放的缓冲区,通常是当您的直接NIO缓冲区不是ByteBuffer时。

注意:sun.misc.Cleaner已经被移动到Java 1.9模块“java.base”中的jdk.internal.ref.Cleaner中,后者实现了java.lang.Runnable(感谢Alan Bateman提醒我这种差异)很短的时间,但现在已经不是这样了。你必须调用 sun.misc.Unsafe.invokeCleaner(),它是在 JogAmp 的 Gluegen 中完成的。我更喜欢使用清洁剂作为Runnable,因为它避免了依赖sun.misc.Unsafe,但它现在不起作用。

我的最后一个建议适用于Java 9,10,11和12

我的最新示例需要使用孵化功能(需要Java >= 14),但非常简单。

Lucene中有一个很好的例子,在一个更宽松的许可证下。


答案 2

直接缓冲区很棘手,并且没有通常的垃圾回收保证 - 有关详细信息,请参阅:http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct

如果您遇到问题,我建议分配一次并重新使用缓冲区,而不是重复分配和释放。