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

时间:2011-12-11 05:41:05

标签: java memory memory-management garbage-collection

JDK提供了分配所谓的直接ByteBuffers的能力,其中内存在Java堆之外分配。这可能是有益的,因为垃圾收集器不会触及这个内存,因此不会导致GC开销:这对于像缓存这样的长期存在的东西来说非常有用。

但是,现有实现存在一个严重问题:当拥有ByteBuffer被垃圾收集时,底层内存只是异步分配;没有办法强迫早期解除分配。这可能会有问题,因为GC循环本身不受ByteBuffers处理的影响,并且假设ByteBuffers可能驻留在Old Generation内存区域,则可能在ByteBuffer不再使用后数小时调用GC。

但理论上应该可以直接使用sun.misc.Unsafe方法(freeMemory,allocateMemory):这就是JDK本身用于分配/解除分配本机内存的方法。 看一下代码,我看到一个潜在的问题是内存的双重释放的可能性 - 所以我想确保状态得到适当的清理。

有人能指出我这样做的代码吗?理想情况下,我们希望使用它而不是JNA。

注意:我看到this question有点相关。

看起来指出的答案是很好的方法:here是Elastic Search中使用该想法的代码示例。谢谢大家!

3 个答案:

答案 0 :(得分:25)

有一种更简单的清理记忆的方法。

public static void clean(ByteBuffer bb) {
    if(bb == null) return;
    Cleaner cleaner = ((DirectBuffer) bb).cleaner();
    if (cleaner != null) cleaner.clean();
}

如果你相当快地丢弃直接或内存映射的ByteBuffer,使用它会产生很大的不同。

使用Cleaner执行此操作的原因之一是您可以拥有底层内存资源的多个副本,例如:用slice()。清洁工具有这些资源数量。

答案 1 :(得分:5)

使用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();
}

答案 2 :(得分:2)

基本上你想要的是遵循与使用IO流相同的语义。就像你需要关闭一次流,你需要释放一次内存。因此,您可以围绕本机调用编写自己的包装器,从而尽早释放内存