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);//释放

 

posted @ 2017-07-31 23:17  holoyong  阅读(1365)  评论(0编辑  收藏  举报