java的Reference与DirectBuffer的内存回收

这篇文章出现的原因在于自己一直困惑于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();
    }

这个是定义在reference里面的一段代码,从上面注释就可以知道。。。对于所有需要放入到queue里面的reference将会先在这里连接到pending后面,形成一个链表。。。这个可能是gc做的吧。。

而在下面定义了一个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特殊的对待了。。。

不懂。。。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值