java基础-DirectByteBuffer
我们可以通过ByteBuffer创建一块直接内存
ByteBuffer direct = ByteBuffer.allocateDirect(8);//创建8字节空间的直接内存
来看这块内存是如何被分配的
1 public static ByteBuffer allocateDirect(int capacity) { 2 return new DirectByteBuffer(capacity); 3 }
1 DirectByteBuffer(int cap) { // package-private 2 //mark, position, limit, capacity 3 super(-1, 0, cap, cap); 4 //后面的一大堆就是计算分配空间大小、分配、计算空间始址 5 boolean pa = VM.isDirectMemoryPageAligned(); 6 int ps = Bits.pageSize(); 7 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); 8 Bits.reserveMemory(size, cap); 9 10 long base = 0; 11 try { 12 base = unsafe.allocateMemory(size);//使用unsafe来分配直接内存 13 } catch (OutOfMemoryError x) { 14 Bits.unreserveMemory(size, cap); 15 throw x; 16 } 17 unsafe.setMemory(base, size, (byte) 0); 18 if (pa && (base % ps != 0)) { 19 // Round up to page boundary 20 address = base + ps - (base & (ps - 1)); 21 } else { 22 //address是始址,可知一个DirectByteBuffer对象存储了内存始址address、内存容量capacity,这已经可以确定一块内存了,再加上position、limited、mark就可以对该内存进行缓存式的读写操作了 23 address = base; 24 } 25 cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//用于回收直接内存 26 att = null;//attachement 27 }
对于内存空间,我们关注的是它的分配和回收,这里使用了unsafe分配,unsafe是一个提供了低等级操作的接口,这里就不研究它了,我们主要来看这块被unsafe分配的直接内存是如何被回收的。
我们知道,ByteBuffer和普通java对象一样,是通过gc回收的,但gc并不管理直接内存,ByteBuffer指向的直接内存空间是如何被释放的呢?
重点来看Cleaner.create(this, new Deallocator(base, size, cap))
Deallocator实现了Runnable,在run方法中使用unsafe.freeMemory(address)释放了内存。
1 private static class Deallocator 2 implements Runnable 3 { 4 5 private static Unsafe unsafe = Unsafe.getUnsafe(); 6 7 private long address; 8 private long size; 9 private int capacity; 10 11 private Deallocator(long address, long size, int capacity) { 12 assert (address != 0); 13 this.address = address; 14 this.size = size; 15 this.capacity = capacity; 16 } 17 18 public void run() { 19 if (address == 0) { 20 // Paranoia 21 return; 22 } 23 unsafe.freeMemory(address); 24 address = 0; 25 Bits.unreserveMemory(size, capacity); 26 } 27 28 }
1 public static Cleaner create(Object var0, Runnable var1) { 2 //add是将cleaner实例存入由Cleaner维护的一个链表中,这里的var0是DirectByteBuffer,var1是Deallocator 3 return var1 == null ? null : add(new Cleaner(var0, var1)); 4 } 5 private Cleaner(Object var1, Runnable var2) { 6 //Cleaner继承了PhantomReference,dummyQueue是一个假队列,无用。这里将DirectByteBuffer作为PhantomReference,Deallocator为thunk 7 super(var1, dummyQueue); 8 this.thunk = var2; 9 }
之前有聊过Reference机制对于Cleaner的特殊处理,当HandlerThread从pending队列中取到cleaner后,会执行其clean方法。下面就是clean方法,其中调用了thunk.run,该thunk对应Deallocator,run方法中就包含了unsafe.freeMemory,就此直接内存被释放了。
1 public void clean() { 2 if (remove(this)) {//从链表中删除该cleaner 3 try { 4 this.thunk.run();//执行Runnable逻辑 5 } catch (final Throwable var2) { 6 AccessController.doPrivileged(new PrivilegedAction<Void>() { 7 public Void run() { 8 if (System.err != null) { 9 (new Error("Cleaner terminated abnormally", var2)).printStackTrace(); 10 } 11 12 System.exit(1); 13 return null; 14 } 15 }); 16 } 17 } 18 }
总结一下,申请直接内存其实就是构造了一个DirectByteBuffer实例,该实例会持有一个cleaner实例,当不再有强引用指向我们创建的DirectByteBuffer实例时,gc就会回收该实例,与此同时,PhantomReference类型的Cleaner也会被HandlerThread捕获,并执行clean方法,该clean方法会调用thunk.run,对于DirectByteBuffer来说,thunk就是Deallocator,故直接内存得以释放。
由上述可知,我们也可以自己控制直接内存的分配和释放
1 long address = unsafe.allocateMemory(size);//分配 2 unsafe.freeMemory(address);//释放