我们可以通过ByteBuffer创建一块直接内存
ByteBuffer direct = ByteBuffer.allocateDirect(8);//创建8字节空间的直接内存
来看这块内存是如何被分配的
1 public static ByteBuffer allocateDirect(intcapacity) {2 return newDirectByteBuffer(capacity);3 }
1 DirectByteBuffer(int cap) { //package-private2 //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 throwx;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 classDeallocator2 implementsRunnable3 {4
5 private static Unsafe unsafe =Unsafe.getUnsafe();6
7 private longaddress;8 private longsize;9 private intcapacity;10
11 private Deallocator(long address, long size, intcapacity) {12 assert (address != 0);13 this.address =address;14 this.size =size;15 this.capacity =capacity;16 }17
18 public voidrun() {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 staticCleaner create(Object var0, Runnable var1) {2 //add是将cleaner实例存入由Cleaner维护的一个链表中,这里的var0是DirectByteBuffer,var1是Deallocator
3 return var1 == null ? null : add(newCleaner(var0, var1));4 }5 privateCleaner(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 voidclean() {2 if (remove(this)) {//从链表中删除该cleaner
3 try{4 this.thunk.run();//执行Runnable逻辑
5 } catch (finalThrowable var2) {6 AccessController.doPrivileged(new PrivilegedAction() {7 publicVoid 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);//释放