JDK提供了分配所谓的直接ByteBuffers的能力,其中内存在Java堆之外分配。这可能是有益的,因为垃圾收集器不会触及这个内存,因此不会导致GC开销:这对于像缓存这样的长期存在的东西来说非常有用。
但是,现有实现存在一个严重问题:当拥有ByteBuffer被垃圾收集时,底层内存只是异步分配;没有办法强迫早期解除分配。这可能会有问题,因为GC循环本身不受ByteBuffers处理的影响,并且假设ByteBuffers可能驻留在Old Generation内存区域,则可能在ByteBuffer不再使用后数小时调用GC。
但理论上应该可以直接使用sun.misc.Unsafe
方法(freeMemory,allocateMemory):这就是JDK本身用于分配/解除分配本机内存的方法。
看一下代码,我看到一个潜在的问题是内存的双重释放的可能性 - 所以我想确保状态得到适当的清理。
有人能指出我这样做的代码吗?理想情况下,我们希望使用它而不是JNA。
注意:我看到this question有点相关。
看起来指出的答案是很好的方法:here是Elastic Search中使用该想法的代码示例。谢谢大家!
答案 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流相同的语义。就像你需要关闭一次流,你需要释放一次内存。因此,您可以围绕本机调用编写自己的包装器,从而尽早释放内存