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

发布于 2024-09-14 06:08:27 字数 223 浏览 21 评论 0原文

我使用直接缓冲区 (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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

心安伴我暖 2024-09-21 06:08:27

直接 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/

Direct ByteBuffer 对象自动清理其本机缓冲区
但只能将其作为 Java 堆 GC 的一部分 — 所以他们不这样做
自动响应本机堆上的压力。仅发生GC
当 Java 堆变得如此满时,它无法为堆分配提供服务
请求或者 Java 应用程序明确请求它(不是
推荐,因为它会导致性能问题)。

参考: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 已移动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/

Direct ByteBuffer objects clean up their native buffers automatically
but can only do so as part of Java heap GC — so they do not
automatically respond to pressure on the native heap. GC occurs only
when the Java heap becomes so full it can't service a heap-allocation
request or if the Java application explicitly requests it (not
recommended because it causes performance problems).

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

The contents of direct buffers may reside outside of the normal garbage-collected heap

This solution is integrated in Java 14:

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

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:

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

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.

海之角 2024-09-21 06:08:27

直接缓冲区很棘手,并且没有通常的垃圾收集保证 - 有关更多详细信息,请参阅: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.

昵称有卵用 2024-09-21 06:08:27

释放的方式很糟糕——软引用基本上被插入到 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.

深者入戏 2024-09-21 06:08:27

释放直接缓冲区是垃圾收集器在标记 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.

绮烟 2024-09-21 06:08:27

使用 gouessej 的回答中的信息,我能够将这个实用程序类放在一起,以释放给定 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;
        }
    }

}

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.

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;
        }
    }

}
小猫一只 2024-09-21 06:08:27

您可以完全在受支持的公共 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文