堆外内存申请和释放:
堆外内存时不受jvm管理,gc算法不能被回收的。
我们可以使用unsafe类来进行申请和释放:
申请:
// 分配 10M 堆外内存
long address = unsafe.allocateMemory(10 * 1024 * 1024);
释放:unsafe.freeMemory()
使用:
private static Unsafe unsafe = null;
static {
try {
Field getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
unsafe = (Unsafe) getUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
unsafe通过jni来进行调用,调用到c语言的unsafe_allocateMemory。
其实还是调用c的函数:malloc来申请内存
释放的c函数:free
// 分配 10M 堆外内存
ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
跟一下源码: 底层调用的是DirectByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
调用了DirectByteBuffer
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
看到:
base = unsafe.allocateMemory(size);
其实还是调用的unsafe类
那么NIO是如何释放的?主要在于cleaner类:
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
DirectByteBuffer 在初始化时会创建一个 Cleaner 对象,并且 Deallocator 传入到了 cleaner,并且赋值到了thunk
sun.misc.Cleaner#create
public static Cleaner create(Object paramObject, Runnable paramRunnable) {
if (paramRunnable == null)
return null;
return add(new Cleaner(paramObject, paramRunnable));
}
private Cleaner(Object paramObject, Runnable paramRunnable) {
super(paramObject, dummyQueue);
this.thunk = paramRunnable;
}
Cleaner是什么?
我们知道java中的四种引用方式:强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference
Cleaner 就属于 PhantomReference 的子类
sun.misc.Cleaner
public class Cleaner
extends PhantomReference<Object>
那么 虚引用如何使用?
不能直接只用,而是需要使用ReferenceQueue (引用队列)
public class Cleaner extends java.lang.ref.PhantomReference<java.lang.Object> {
private static final java.lang.ref.ReferenceQueue<java.lang.Object> dummyQueue;
private static sun.misc.Cleaner first;
private sun.misc.Cleaner next;
private sun.misc.Cleaner prev;
private final java.lang.Runnable thunk;
public void clean() {}
}
Deallocator有什么用?
这个是用来回收堆外内存的
首先看一下cleaner的 clean()方法
public void clean() {
if (!remove(this))
return;
try {
this.thunk.run();
} catch (Throwable throwable) {
}
}
这个thunk就是 我们创建的时候 传入的 Deallocator 线程
再看一下这行代码 Deallocator:
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
这个是DirectByteBuffer自己创建的线程,调用的是 unsafe.freeMemory(address);
private static class Deallocator
implements Runnable
{
private static Unsafe unsafe = Unsafe.getUnsafe();
private long address;
private long size;
private int capacity;
private Deallocator(long address, long size, int capacity) {
assert (address != 0);
this.address = address;
this.size = size;
this.capacity = capacity;
}
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
当发生 GC 时,DirectByteBuffer 对象被回收
Cleaner 对象不再有任何引用关系,在下一次 GC 时,该 Cleaner 对象将被添加到 ReferenceQueue 中,并执行 clean() 方法。clean() 方法主要做两件事情: