HeapByteBuffer
HeapByteBuffer 是写在jvm堆上面的一个buffer,底层的本质是一个数组,用类封装维护了很多的索引。(limit/position/capacity等),在实际操作时,会把jvm堆上数据拷贝出来到系统内存中,原因是jvm在进行gc(垃圾回收)时,会对数据进行移动,一旦出现这种问题,就会出现数据错乱的情况。
DirectByteBuffer
DirectByteBuffer,底层的数据其实是维护在操作系统的内存中,而不是jvm里,堆外内存,指的时JVM堆以外的内存,这部分内存并不受JVM管控,也就不能被JVM直接回收了。其中DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据,如下:
private static class Deallocator
implements Runnable
{
private static Unsafe unsafe = Unsafe.getUnsafe();
//开辟的堆外内存地址指向
private long address;
}
堆外内存申请内存空间:
//不可以直接被访问
DirectByteBuffer(int cap) {
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 {
//调用native方法分配堆外内存,并返回基地址
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;
}
堆外内存的回收
当直接内存使用完毕,DirectByteBuffer是可以被JVM回收的,但是通过它分配到的堆外内存却不能被JVM回收。那怎么办呢,如果有大量的堆外内存回收不了,就会造成内存泄漏。
堆外内存回收,主要通过两个方面:
从清理器Cleaner入手:
1、从Cleaner的实现得知它是一个虚引用,在从虚引用的定义得知,虚引用必须与一个引用队列相关联,在jvm回收一个对象,发现它有虚引用时,就会把这个对象放入到引用队列中,做一系列操作。
public void clean() {
if (remove(this)) {
try {
this.thunk.run();
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
}
}
clean在Reference弱引用的线程run方法调用
public void run() {
while (true) {
tryHandlePending(true);
}
}
2:unsafe释放内存
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);
}
}
HeapByteBuffer 和 DirectByteBuffer比较
HeapByteBuffer优点:由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收。
DirectByteBuffer优点:跟IO设备打交道时会快很多,因为IO设备读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现零拷贝。
为避免堆外内存造成的内存溢出,通过参数控制:
-XX:MaxDirectMemorySize=500m