在Java中为JOGL释放直接缓冲区本机内存

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

似乎没有好的方法来使用java.nio的缓冲类来解除分配。 我的问题是:

JOGL中有一些删除Direct Buffers的方法吗? 我正在研究glDeleteBuffer(),但看起来这只是从显卡内存中删除缓冲区。

谢谢

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

当您知道直接NIO缓冲区对您无用时,您必须使用其sun.misc.Cleaner(StaxMan是正确的)释放其本机内存并调用clean()(Apache Harmony除外),调用free() (使用Apache Harmony)或使用更好的公共API来做到这一点(可能在Java> = 1.9,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的一部分来执行 – 因此它们不会自动响应本机堆上的压力。 GC仅在Java堆变满时才会发生,它无法为堆分配请求提供服务,或者Java应用程序显式请求它(不建议使用,因为它会导致性能问题)。

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

直接缓冲区的内容可能位于正常的垃圾收集堆之外

这个解决方案 (在这个JEP中 ,仍然是草案,可能在Java 1.9中不可用)非常有前景,我们不需要使用非公共API。

public long memory(long index) { // The scope where the memory region is available // Implements AutoClosable but `close` can be called manually as well try (Scope scope = new NativeScope()) { // Allocate the actual memory area, in this case in the style of a "long-array" Pointer ptr = scope.allocate( NativeLibrary.createLayout(long.class), numElements); // Get the reference to a certain element Reference ref = ptr.offset(index).deref(); // Set a value to this element through the reference ref.set(Long.MAX_VALUE); // Read the value of an element return ref.get(); } } 

注意: sun.misc.Cleaner已经被移植到Java 1.9中的jdk.internal.ref.Cleaner模块“java.base”中,但后者实现了java.lang.Runnable(感谢Alan Bateman提醒我这个区别)。 然后,您只需将清理器强制转换为Runnable并调用方法run() (不要通过reflection调用它以避免获得java.lang.IllegalAccessException)。 它的工作原理,我刚刚使用Java 1.9 Early Access build 129进行了测试(2016年8月6日)。

但是,jdk.internal.ref.Cleaner可能会在以后转移到java.ref.Cleaner $ Cleanable。

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

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

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

解除分配直接缓冲区是垃圾收集器在标记ByteBuffer对象后的某个时间完成的工作。

您可以在删除缓冲区的最后一个引用后立即尝试调用gc。 至少有可能内存会更快一点。

解除分配的方式很糟糕 – 一个软引用基本上被插入一个Cleaner对象,然后在拥有ByteBuffer被垃圾收集时进行解除分配。 但这并不能保证及时调用。

而不是滥用非公开apis的反映,你可以完全在支持的公共api中做到这一点。

写一些用NewDirectByteBuffer包装malloc的JNI(记得设置顺序),以及一个类似的函数来释放它。