Java 使用sun.misc.Unsafe强制释放已分配的本机内存direct ByteBuffer的示例?

Java 使用sun.misc.Unsafe强制释放已分配的本机内存direct ByteBuffer的示例?,java,memory,memory-management,garbage-collection,Java,Memory,Memory Management,Garbage Collection,JDK提供了分配所谓的直接字节缓冲区的能力,其中内存分配在Java堆之外。这可能是有益的,因为垃圾收集器不会触及该内存,因此不会增加GC开销:这是一个非常有用的属性,适用于缓存等长期存在的对象 然而,现有实现存在一个关键问题:只有当拥有的ByteBuffer被垃圾收集时,底层内存才被异步分配;没有办法强制提前解除分配。这可能是有问题的,因为GC循环本身不受ByteBuffers处理的影响,并且考虑到ByteBuffers可能驻留在旧一代内存区域中,所以可能在ByteBuffer不再使用数小时后调

JDK提供了分配所谓的直接字节缓冲区的能力,其中内存分配在Java堆之外。这可能是有益的,因为垃圾收集器不会触及该内存,因此不会增加GC开销:这是一个非常有用的属性,适用于缓存等长期存在的对象

然而,现有实现存在一个关键问题:只有当拥有的ByteBuffer被垃圾收集时,底层内存才被异步分配;没有办法强制提前解除分配。这可能是有问题的,因为GC循环本身不受ByteBuffers处理的影响,并且考虑到ByteBuffers可能驻留在旧一代内存区域中,所以可能在ByteBuffer不再使用数小时后调用GC

但从理论上讲,直接使用
sun.misc.Unsafe
方法(freeMemory、allocateMemory)是可能的:这就是JDK本身用于分配/取消分配本机内存的方法。 看看代码,我看到的一个潜在问题是内存的双重释放的可能性——所以我想确保状态被正确清理

有人能告诉我做这件事的代码吗?理想情况下,您可能希望使用它而不是JNA

注意:我看到了有点相关的


看起来,所指出的答案是一个很好的方法:代码示例来自使用该思想的弹性搜索。谢谢大家

基本上,您需要遵循与使用IO流相同的语义。就像您需要关闭一次流一样,您也需要释放一次内存。因此,您可以围绕本机调用编写自己的包装器,以便使用
sun.misc.Unsafe
尽早释放内存,这几乎是不可能的,因为分配的本机内存的基址是
java.nio.DirectByteBuffer
构造函数的局部变量

实际上,您可以使用以下代码强制释放本机内存:

import sun.misc.Cleaner;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;

...

public static void main(String[] args) throws Exception {
    ByteBuffer direct = ByteBuffer.allocateDirect(1024);
    Field cleanerField = direct.getClass().getDeclaredField("cleaner");
    cleanerField.setAccessible(true);
    Cleaner cleaner = (Cleaner) cleanerField.get(direct);
    cleaner.clean();
}

有一种更简单的方法来清理内存

public static void clean(ByteBuffer bb) {
    if(bb == null) return;
    Cleaner cleaner = ((DirectBuffer) bb).cleaner();
    if (cleaner != null) cleaner.clean();
}
如果您相当快地放弃direct或内存映射ByteBuffer,那么使用它会产生很大的不同


使用清理器执行此操作的原因之一是,您可以拥有基础内存资源的多个副本,例如,使用slice()。而且清洁工有这些资源的计数。

这是可能的,在这么多年前的。。。现在看看。(在过去,我只是使用一个循环的缓冲队列,这使我的情况下压力很低。)哇,我现在到处都找不到好文章:(我还没有看到System.gc()被忽略,至少在Sun上,它似乎是目标。有一个系统属性可以让JVM忽略它,这是值得的。最后一个链接已经死了。嗯。我在看cleaner,但不确定访问权限是否允许我直接调用它……但如果是这样,这肯定是一个好方法。谢谢——也许我太匆忙了放弃这种方法!我在许多项目中使用过这种方法。它比使用反射IMHO更好。我的答案是通过反射进行cleaner.clean。有些人可能更喜欢不需要直接导入任何sun.*包。sun.nio.ch.DirectBuffer(与sun.misc.cleaner不同)Java 1.9默认情况下不再使用,请参阅JEP 260。也许为了弥补这一点,Java 9中的
sun.misc.Unsafe
添加了一个
void invokeCleaner(ByteBuffer)
方法。是的,在非常高的级别上,但是您已经详细了解了具体的操作方法了吗?这就是问题的根源:没有一种方法可以使用(直接)ByteBuffers,no close()方法。不幸的是,即使在幕后,事情也相当复杂。是的,但您可以通过使用不安全的方法实现自己的缓冲区来改变游戏。(这可能对填充内存非常有效,以避免错误共享,但这是一个完全不同的游戏)没错。在我的例子中,我完全控制所有访问,因此底层原始存储既不会暴露,也不应该有挂起引用的危险。对,这就是我问的原因;Cleaner与Deallocator回调的整个编排是复杂的。我将尝试这种方法,假设类型Cleaner是可访问的(我注意到它调用的东西不是)。你不需要为此滥用反射。只需检查类型并使用显式强制转换,就像Peter Lawrey提出的解决方案一样。