这篇文章出现的原因在于自己一直困惑于java中对DirectBuffer的内存的回收,知道它是可以经由java虚拟机自动管理的,不需要我们自己去手动的释放,但是要知道DirectBuffer的内存独立于jvm,而是直接调用的malloc来分配的,那么也必定需要free来释放。。。莫非虚拟机的gc还有这功能。。。?
其实不然,最终采用的是一种折中的方法来实现的。。。。
在将具体的实现之前先要对java中的Reference比较了解了才可以。。。。
Reference主要是分为4中类型:
(1)strong reference,这个也是我们平时用到的最多的,我们平时申明一个变量,那么它默认就是这种类型,对于这种类型应用的java对象,当且只当当前的运行栈中没有它的引用之后,它就将会被gc掉。。。这也就是我们说的最多的垃圾回收。。
(2)SoftReference。。这是一种比strong reference弱一些的引用类型,当当前虚拟机内存很紧张的时候,那么gc将会将其引用的对象给回收掉。。这也就是说就算我们对对象用SoftReference引用,但是它也可能会被gc给回收了。。。所以这种引用类型非常适合用于实现高速缓存。。。。
(3)WeakReference,它比上面提到的SoftReference还要弱一些。。。只要gc运行了,它被引用的对象就会被回收。。可以用下面的例子来说明:
public class Fjs { static public Logger logger = Logger.getLogger(Fjs.class); public static void main(String args[]) { /*logger.debug("aaaaa"); ByteBuffer buffer = ByteBuffer.allocateDirect(10000); PhantomReference ref = new PhantomReference (buffer, null); Reference ref2 = new WeakReference(buffer); ref.get();*/ Reference ref = new WeakReference(new Fjs()); System.out.println(ref.get()); //打印出引用的对象 System.gc(); System.out.println(ref.get()); } }
输出的结果是:
fjs.Fjs@5ca0b138
null
由此可见,gc之后引用的Fjs类型的对象被回收掉了。。。
但是如果我们将WeakReference换成了SoftReference了之后,如下:
public static void main(String args[]) { /*logger.debug("aaaaa"); ByteBuffer buffer = ByteBuffer.allocateDirect(10000); PhantomReference ref = new PhantomReference (buffer, null); Reference ref2 = new WeakReference(buffer); ref.get();*/ Reference ref = new SoftReference(new Fjs()); System.out.println(ref.get()); //打印出引用的对象 System.gc(); System.out.println(ref.get()); }
输出的结果将会是:
fjs.Fjs@704d4834
fjs.Fjs@704d4834
这里也就可以得出结论SoftReference要比WeakReference强一些。。。。
(4)Phantomreference,这个是最神奇的了。。。代码如下:
public static void main(String args[]) { /*logger.debug("aaaaa"); ByteBuffer buffer = ByteBuffer.allocateDirect(10000); PhantomReference ref = new PhantomReference (buffer, null); Reference ref2 = new WeakReference(buffer); ref.get();*/ Reference ref = new PhantomReference(new Fjs(), null); System.out.println(ref.get()); //打印出引用的对象 }
这里还没有执行gc,输出就会使null,也就是木有啦。。其实不然。。我们来看看 PhantomReference的定义吧:
public class PhantomReference<T> extends Reference<T> { /** * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * <code>null</code>. * * @return <code>null</code> */ public T get() { return null; } /** * Creates a new phantom reference that refers to the given object and * is registered with the given queue. * * <p> It is possible to create a phantom reference with a <tt>null</tt> * queue, but such a reference is completely useless: Its <tt>get</tt> * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, * or <tt>null</tt> if registration is not required */ public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
这里可以看到它将get方法重载为直接返回null,因此可以知道,如果我们有对象被PhantomReference引用的话,如果还想用它,那么就还需要维持一个它的其他类型的引用,向上面那样,直接传一个匿名对象进去,那么就再也不要想获取那个对象的引用了。。。另外这里构造函数中还需要一个ReferenceQueue,它的作用也很重要。。当我们引用的对象被回收之后,这个PhantomReference对象本身将会被放到这个队列中去。。。。这件事情是gc来干的。。我们就不去深究了。。只要知道是这个样子实现的就好了。。。
我们可以用下面的例子来证明:
public static void main(String args[]) throws IllegalArgumentException, InterruptedException { /*logger.debug("aaaaa"); ByteBuffer buffer = ByteBuffer.allocateDirect(10000); PhantomReference ref = new PhantomReference (buffer, null); Reference ref2 = new WeakReference(buffer); ref.get();*/ ReferenceQueue queue = new ReferenceQueue(); Reference ref = new PhantomReference(new Fjs(), queue); System.out.println(ref); System.out.println(queue.remove(100)); System.gc(); System.out.println(queue.remove(100)); }
输出的结果将会是:
java.lang.ref.PhantomReference@4c6ca32e
null
java.lang.ref.PhantomReference@4c6ca32e
可以看到ref在gc之后被加入到了queue里面了。。。。
到了这里就会考虑PhantomReference有啥用了吧。。。那么这里就可以结合DirectBuffer的回收来说了。。哈哈。。。
首先我们来看看DirectBuffer的创建吧:
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; }
这个是整个DirctBuffer的创建过程。。这里可以看到其实是调用unsafe对象来分配内存,然后这里会记录下分配的内存的位置,还要创建一个非常重要的cleaner对象。。。。而且在创建cleaner对象的时候还创建了一个Deallocator对象。。。这两个东西将会是DirectBuffer释放申请的内存的非常关键的两个东西。。。
而且这里可以看到底层用到的内存是通过unsafe对象分配的,擦。。这个就没有办法了。。。不过无非就是通过malloc啥的分配内存嘛。。。。
那么我们来看看这个cleaner对象是个啥东西吧。。。。
好吧,sun.misc.Cleaner,我勒个去。。又看不了。。不过这里可以知道它继承自PhantomReference。。。呵呵。。。呵呵。。。
那么我们就可以知道当其会被加入到一个queue里面。。那么关键就在这个地方了。。。
来看一段代码:
/* List of References waiting to be enqueued. The collector adds * References to this list, while the Reference-handler thread removes * them. This list is protected by the above lock object. */ private static Reference pending = null; /* High-priority thread to enqueue pending References */ private static class ReferenceHandler extends Thread { ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } } } static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); }
而在下面定义了一个Thread,它要做的事情。。不断的遍历这个链表,将后面的reference放到其应该去的queue里面去,不过这里就还有一段其他的代码。。
判断它是否是cleaner类型的,如果是的话,那么将会执行它的clean方法。。。
而且可以看到这个Thread在后面被启动了。。。而且将其设置为后台线程。。当虚拟机启动后它就会被启动。。。。
那么到这里其实就基本知道了DirectBuffer里面申请的内存是怎么自动被释放的了吧。。。由于clean方法我们看不到代码。。不过其实主要你也就是执行Deallocator的run方法:
public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }
也就是再次调用unsafe将以前申请的内存释放掉。。。。
到这里也就可以知道。当我们定义DirectBuffer类型的对象被释放之后,其底层申请的直接内存也会自动的被释放。。不过这个自动释放的过程算是比较的曲折了。。。。
不过这里有一个问题。。。不知道怎么回事。。。按照道理来说PhantomReference,只要gc启动了,那么PhantomReference类型的对象就应该被gc放到pending链表,然后再由那个后台线程来处理。。。
那么如果真是这样子的话,那么只要gc我们申请的DirectBuffer底层的内存就会被释放掉。。。
但是实际情况不是这样子的,如果我们DirectBuffer对象没有被释放。。就算是gc,底层申请的内存也不会被释放。。那么也就是说gc并没有把继承自PhantomReference的cleaner放到pending链表了。。。这个就与PhantomReference有一定的冲突了吧。。。不过也有可能Cleaner类型做了一些其余的事情吧。。。或者说gc对于Cleaner特殊的对待了。。。
不懂。。。。。