在 Java 中为 JOGL 释放直接缓冲区本机内存
Posted
技术标签:
【中文标题】在 Java 中为 JOGL 释放直接缓冲区本机内存【英文标题】:Deallocating Direct Buffer Native Memory in Java for JOGL 【发布时间】:2010-08-16 19:18:43 【问题描述】:我正在使用直接缓冲区 (java.nio) 来存储 JOGL 的顶点信息。这些缓冲区很大,并且在应用程序生命周期中会多次更换。内存没有及时释放,经过几次更换后内存不足。
似乎没有使用 java.nio 的缓冲区类来解除分配的好方法。我的问题是这样的:
在 JOGL 中有什么方法可以删除 Direct Buffers 吗?我正在研究 glDeleteBuffer(),但似乎这只会从视频卡内存中删除缓冲区。
谢谢
【问题讨论】:
【参考方案1】:直接 NIO 缓冲区使用非托管内存。这意味着它们是在本机堆上分配的,而不是在 Java 堆上。因此,只有当 JVM 在 Java 堆上用完内存时,它们才会被释放,而不是在本机堆上。换句话说,它是不受管理的=由您来管理它们。不鼓励强制进行垃圾回收,而且大多数情况下不会解决这个问题。
当你知道一个直接的 NIO 缓冲区对你来说变得无用时,你必须通过使用它的 sun.misc.Cleaner 释放它的本机内存(StaxMan 是对的)并调用 clean()(Apache Harmony 除外),调用free() (使用 Apache Harmony)或使用更好的公共 API 来执行此操作(可能在 Java > 12 中,扩展 AutoCloseable 的 AutoCleaning?)。
这不是 JOGL 的工作,您可以使用纯 Java 代码自己完成。 My example 在 GPL v2 下,this example 在更宽松的许可证下。
编辑:My latest example 甚至可以使用 Java 1.9,并支持 OpenJDK、Oracle Java、Sun Java、Apache Harmony、GNU Classpath 和 android。您可能必须删除一些语法糖才能使其与 Java
参考: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 已集成在 Java 14 中:
try (MemorySegment segment = MemorySegment.allocateNative(100))
...
您可以通过调用MemorySegment.ofByteBuffer(ByteBuffer) 将字节缓冲区包装到内存段中,获取其内存地址并在 Java 16 中释放它(这是一种受限方法):
CLinker.getInstance().freeMemoryRestricted(MemorySegment.ofByteBuffer(myByteBuffer).address());
请注意,在许多重要的情况下,您仍然需要使用反射来找到可以释放的缓冲区,通常是当您的直接 NIO 缓冲区不是 ByteBuffer 时。
N.B:sun.misc.Cleaner 已被移动到 Java 1.9 中的“java.base”模块中的jdk.internal.ref.Cleaner,后者实现了 java.lang.Runnable(感谢 Alan Bateman提醒我区别)很短的时间,但it's no longer the case。你必须调用 sun.misc.Unsafe.invokeCleaner(),它是 done in JogAmp's Gluegen。我更喜欢将 Cleaner 用作 Runnable,因为它避免了依赖 sun.misc.Unsafe,但它现在不起作用。
我的最后一个建议适用于 Java 9、10、11 和 12。
My very latest example 需要使用孵化功能(需要 Java >= 14)但非常简单。
a good example in Lucene 在更宽松的许可证下。
【讨论】:
+1:这实际上与JOGL
无关,我可以在这里找到关于释放 java nio 缓冲区的最佳参考
您列表中的最后一个解决方案可能还没有为 Java 9 做好准备。JEP 仍然是一个草案。 openjdk.java.net/jeps/191
@LukeHutchison sun.misc.Unsafe 包含名为 invokeCleaner(java.nio.ByteBuffer) 的方法,而 jdk.internal.misc.Unsafe 没有。因此,这并不完全是重命名。
我注意到一件事:在 JDK9+ 中,调用 sun.misc.Cleaner.clean() 会在 stderr 上发出反射(封装)警告。 Unsafe.theUnsafe.invokeCleaner(byteBuffer) 进行相同的调用,但不打印反射警告。
@LukeHutchison 您只需要在使用 MemorySegment.close() 时使用 attachment(),现在简单多了,但我仍然收到警告。【参考方案2】:
直接缓冲区很棘手,并且没有通常的垃圾收集保证 - 请参阅:http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html#direct
如果您遇到问题,我建议分配一次并重新使用缓冲区,而不是反复分配和解除分配。
【讨论】:
你最后的建议很好。缓冲区在多次使用时非常有用。也可以切片。但是,通过保留未使用的已分配本机内存,存在增加运行时内存占用的风险。如果正确实施,直接 NIO 缓冲区的释放比试图避免重复分配和释放它们更有效且侵入性更小。【参考方案3】:完成释放的方式很糟糕——一个软引用基本上被插入到一个 Cleaner 对象中,然后当拥有 ByteBuffer 被垃圾回收时它会进行释放。但这并不能真正保证及时调用。
【讨论】:
你说它很糟糕,但 AFAIK 是尽你所能。直接缓冲区是有意从托管堆中分配出来的,所以我们不应该对它们的管理有点缺乏感到惊讶。无论如何,在抛出 OOM 之前,会尝试处理所有挂起的引用并运行 GC。它可能无法按设计工作。 +++ 恐怕,除非我们解决Unsafe
并冒类似 malloc/free 的泄漏和崩溃的风险,否则我们只能得到这些。【参考方案4】:
释放直接缓冲区是垃圾收集器在标记 ByteBuffer 对象后的某个时间完成的工作。
您可以尝试在删除对缓冲区的最后一个引用后立即调用 gc。至少有可能内存会被释放得更快一点。
【讨论】:
“调用 gc”是指System.gc()
我猜;这确实是一个长镜头,因为不能保证这个电话真的能做任何事情。确保在调用之前设置对直接缓冲区= null
的任何引用,否则 GC 肯定会认为缓冲区仍在使用中。【参考方案5】:
使用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;
【讨论】:
【参考方案6】:您可以完全在受支持的公共 API 内轻松完成此操作,而不是滥用非公共 API 的反射。
编写一些 JNI,它用 NewDirectByteBuffer 包装 malloc(记得设置顺序),以及一个类似的函数来释放它。
【讨论】:
我同意 ochi 并查看我帖子中的最后一个建议,它不使用任何非公共 API。以上是关于在 Java 中为 JOGL 释放直接缓冲区本机内存的主要内容,如果未能解决你的问题,请参考以下文章