代码之家  ›  专栏  ›  技术社区  ›  sparrow400

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

  •  9
  • sparrow400  · 技术社区  · 16 年前

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

    5 回复  |  直到 16 年前
        1
  •  21
  •   gouessej    5 年前

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

    当您知道直接NIO缓冲区对您来说已经无用时,您必须使用它的sun.misc.Cleaner(StaxMan是对的)释放它的本机内存,并调用clean()(Apache Harmony除外)、call free()(Apache Harmony)或使用更好的公共API(可能在Java中>12、扩展自动关闭功能的自动清洗。

    这不是JOGL的工作,您可以使用普通Java代码自己完成。 My example 在GPL v2和 this example 是在一个更宽松的许可下。

    My latest example 甚至可以与Java1.9一起使用,并且支持OpenJDK、Oracle Java、Sun Java、Apache Harmony、GNU类路径和Android。您可能需要删除一些语法上的糖分才能使其与Java一起工作<1.7(多重捕获、钻石和仿制药)。

    参考: http://www.ibm.com/developerworks/library/j-nativememory-linux/

    但只能作为Java堆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时。

    注: sun.misc.Cleaner已被移动 进入 jdk.internal.ref.Cleaner 在模块“Java.base”中的Java1.9中,后者实现了Java.lang.Runnable(感谢Alan Bateman提醒我这一区别),时间很短,但是 it's no longer the case . 您必须调用sun.misc.Unsafe.invokeCleaner(),这是 done in JogAmp's Gluegen . 我更喜欢使用清洁剂作为一种可运行的清洁剂,因为它避免了依赖sun.misc.Unsafe,但它现在不起作用。

    我的最后一个建议适用于Java9、10、11和 12

    My very latest example 需要使用孵化功能(需要Java>=14) 但是非常非常简单。

    a good example in Lucene 在更宽松的许可下。

        2
  •  3
  •   gouessej    12 年前

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

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

        3
  •  2
  •   StaxMan    14 年前

    解除分配的方式很糟糕——软引用基本上被插入到一个更干净的对象中,当拥有ByteBuffer的对象被垃圾收集时,该对象就会进行解除分配。但这并不能保证能及时调用。

        4
  •  0
  •   Andreas Dolk    16 年前

    释放直接缓冲区是垃圾收集器在标记ByteBuffer对象后的一段时间内完成的一项工作。

        5
  •  0
  •   Brian_Entei    6 年前

    在网络中使用信息 gouessej's answer ,我能够将这个实用程序类放在一起,以释放给定ByteBuffer的直接内存分配。当然,这只能作为最后的手段,不应该真正用于生产代码中。

    在JavaSE版本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
  •  -1
  •   visitor    10 年前

    与滥用非公共API的反射不同,您可以完全在受支持的公共API中轻松地实现这一点。

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