directByteBuffer-堆外内存申请和释放

内存申请

由于堆外内存分配后无法在heap区管理, 所以用一个变量 reservedMemory来记录已经分配了多少堆外内存 , 下面看下分配的逻辑

static void reserveMemory(long size) {  
  
    synchronized (Bits.class) {  
        if (!memoryLimitSet && VM.isBooted()) {  
            maxMemory = VM.maxDirectMemory();  
            memoryLimitSet = true;  
        }  
        if (size <= maxMemory - reservedMemory) {   //等价  size + reservedMemory  <=  Max  ,
            reservedMemory += size;  
            return;  
        }  else{ //自己加的 好理解 size + reservedMemory  > Max  超出了极限
        	下面会进行gc
        }
    }  
  
    System.gc();   
    try {  
        Thread.sleep(100);    //等待full gc 对堆外内存的回收 , 所以这里会有时间的延迟, 对时间敏感的应用可能接受不了100ms的等待 , 可以用netty 池化的堆外内存
    } catch (InterruptedException x) {  
        // Restore interrupt status  
        Thread.currentThread().interrupt();  
    }  
    synchronized (Bits.class) {  
        if (reservedMemory + size > maxMemory)    // gc后内存还不够 就抛出oom
            throw new OutOfMemoryError("Direct buffer memory");  
        reservedMemory += size;  
    }  
  
}

 

内存的回收

首先看下directbytebuffer的构造方法 ,实例一个清理器,该清理器其实内包含一个runnable , 具体的清理工作就在runnable里进行了

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));   //实例一个清理器,该清理器其实内包含一个runnable , 具体的清理工作就在runnable里进行了
        att = null;
  }

 

下面就要看清理器什么时候调用了 , 我们来看 cleaner 这个类的实现  misc包下的类可以用grepcode来看

这里只看我们关心的几个地方

 

public class Cleaner  extends PhantomReference {

 		private static final ReferenceQueue dummyQueue = new ReferenceQueue();


 		 private Cleaner(Object referent, Runnable thunk) {
	     	super(referent, dummyQueue);
    	 	this.thunk = thunk;
   		}


   		public static Cleaner create(Object ob, Runnable thunk) {
		     if (thunk == null)
		           return null;
		      return add(new Cleaner(ob, thunk));
		 }


		  public void clean() {
		   if (!remove(this))
		       return;
		   try {
		        thunk.run();
		   } catch (final Throwable x) {
		     //....    dosth
			}
		}

 }

 

看到这里基本就明了了,  cleaner 是一个虚引用, 当垃圾回收的时候若发现该引用相关的目标对象, 即这里的referent可达性发生变化时 ,jvm会把该引用放到引用对象内部的一个pending队列里,

经过referenceHandler 线程放到 referenceQueue 即这里的dummyQueue, 这里的dummyQueue没有其他用途,只为不为空。

下面看下 ReferenceHandler线程的run方法

    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);
            }
       }

 

我们发现在处理pending队列的时候 有一个特殊的判断, 如果队列元素是 cleanner类型, 就执行 Cleaner的clean 方法 ,
clean方法就会调用我们在 directbytebuffer方法里构建的 Deallocator的run方法 , 就会执行我们的定义的清理工作 unsafa.free(xx)  , 就会对堆外内存进行释放

 

这里隐藏一个问题,因为我们分配堆外内存在队内只对应一个directByteBuffer对象来记录堆外内存的大小,地址,偏移量等信息  是一个很小的对象 , 理想情况下是该对象在年轻代被回收 , 如果这个小对象跑到了老年代, 可能会造成很长时间为被回收也就造成了堆外内存的泄漏 ,  很明显的现象是 heap区剩余空间很大,却报出了OOM

 

 

 

转载于:https://my.oschina.net/bihu/blog/1068760

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值