我使用直接缓冲区(java.nio)来存储JOGL的顶点信息。这些缓冲区很大,在应用程序生命周期中它们会被更换几次。内存未及时解除分配,并且在几次更换后我的内存不足。
似乎没有好的方法来释放使用java.nio的缓冲区类。我的问题是:
JOGL中有一些删除直接缓冲区的方法吗?我正在研究glDeleteBuffer(),但似乎这只是从显卡内存中删除缓冲区。
由于
答案 0 :(得分:14)
直接NIO缓冲区使用非托管内存。这意味着它们是在本机堆上分配的,而不是在Java堆上。因此,只有当JVM在Java堆上耗尽内存而不是在本机堆上时,它们才会被释放。换句话说,它不受管理=由您来管理它们。不鼓励强制垃圾收集,并且大多数时候都不会解决这个问题。
当您知道直接NIO缓冲区对您无用时,您必须使用其sun.misc.Cleaner(StaxMan是正确的)释放其本机内存并调用clean()(Apache Harmony除外),调用free()(使用Apache Harmony)或使用更好的公共API来执行此操作(可能在Java> = 1.9,AutoCleaning扩展AutoCloseable?)。
这不是JOGL的工作,你可以使用普通的Java代码自己完成。 My example属于GPL第2版,this example属于更宽松的许可。
编辑:My latest example甚至可以与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
直接缓冲区的内容可能位于正常的垃圾回收堆
之外
This solution(在此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<Long> ptr = scope.allocate(
NativeLibrary.createLayout(long.class), numElements);
// Get the reference to a certain element
Reference<Long> 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();
}
}
N.B: sun.misc.Cleaner已被移动到模块中的Java 1.9中的jdk.internal.ref.Cleaner&#34; java.base&#34;但是后者实现了java.lang.Runnable(感谢Alan Bateman提醒我这个区别)。然后,您只需将清理器强制转换为Runnable并调用方法run()
(不要通过反射调用它以避免获得java.lang.IllegalAccessException)。它的工作原理,我刚刚使用Java 1.9 Early Access build 129进行了测试(2016年8月6日)。
但是,jdk.internal.ref.Cleaner可能会在以后转移到java.ref.Cleaner $ Cleanable。
在更宽松的许可下有a good example in Lucene。
答案 1 :(得分:3)
直接缓冲区很棘手,没有通常的垃圾收集保证 - 详情请参阅:http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
如果您遇到问题,我建议分配一次并重新使用缓冲区,而不是重复分配和解除分配。
答案 2 :(得分:1)
重新分配的方式很糟糕 - 软引用基本上被插入到Cleaner对象中,然后在拥有ByteBuffer时进行解除分配是垃圾回收。但这并不能保证及时得到调用。
答案 3 :(得分:0)
取消分配直接缓冲区是垃圾收集器在标记ByteBuffer对象后的某个时间完成的工作。
您可以在删除缓冲区的最后一个引用后立即尝试调用gc。至少有可能会更快地释放内存。
答案 4 :(得分:0)
使用gouessej's answer中的信息,我能够将这个实用程序类放在一起,以释放给定ByteBuffer的直接内存分配。当然,这只能用作不得已的方法,而实际上不应在生产代码中使用。
经过测试并可以在Java SE版本10.0.2中使用。
public final class BufferUtil {
//various buffer creation utility functions etc. etc.
protected static final sun.misc.Unsafe unsafe = AccessController.doPrivileged(new PrivilegedAction<sun.misc.Unsafe>() {
@Override
public Unsafe run() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
});
/** Frees the specified buffer's direct memory allocation.<br>
* The buffer should not be used after calling this method; you should
* instead allow it to be garbage-collected by removing all references of it
* from your program.
*
* @param directBuffer The direct buffer whose memory allocation will be
* freed
* @return Whether or not the memory allocation was freed */
public static final boolean freeDirectBufferMemory(ByteBuffer directBuffer) {
if(!directBuffer.isDirect()) {
return false;
}
try {
unsafe.invokeCleaner(directBuffer);
return true;
} catch(IllegalArgumentException ex) {
ex.printStackTrace();
return false;
}
}
}
答案 5 :(得分:-1)
不是滥用非公开api的反映,你可以完全在支持的公共api中做到这一点。
编写一些用NewDirectByteBuffer包装malloc的JNI(记得设置顺序),以及一个类似的函数来释放它。