• nio DirectByteBuffer如何回收堆外内存


    概述

    使用了nio框架的应用,比如服务框架,利用nio建立长连接通信,他们会使用DirectByteBuffer来分配堆外内存,也就是本地直接内存,这个内存的回收不由gc直接维护,我们通常所说的gc,只回收jvm的堆、方法区。本地内存如果没有用jvm启动参数手动指定,它会根据主机的剩余可用内存进行分配,如果说一个机器的8G内存的,其中,我们手动指定的jvm堆、方法区内存为2048 + 256,那么,除了其他进程占用的内存,剩余的可用内存可能是较大的。如果你的主机有内存使用量监控(不是jvm级的内存监控),在使用类似Netty这种通信框架时,有可能会触发主机内存使用率报警。

    堆外内存回收方法

    • 首先强调,gc不会直接回收堆外内存,堆外内存如果不通过启动参数指定,会根据主机的剩余可用内存来作为容量,这有可能是一块很大的内存,gc回收代价可能较大

    • DirectByteBuffer对象在堆内生成时,会和一个RefereceQueue建立虚引用联系,这里是通过Cleaner对象的某个field被赋值为DirectByteBuffer对象来建立虚引用的,注意,这里是Cleaner对象虚引用了DirectByteBuffer对象,引用queue为cleaner中的dummyQueue。再次强调,cleaner对象虽然虚引用了DirectByteBuffer,但是垃圾回收在计算对象可达性时,会忽略Cleaner对的DirectByteBuffer虚引用,DirectByteBuffer只有可以,就可以立即回收,无视Cleaner对象当前是存活状态。

    • jdk实现GC时,对几种引用类型有定制化开发,在对象B被回收后,会通知到一个ReferenceHandler线程,获取到虚引用对象A,判断A是否是Cleaner,如果是就会调用Cleaner.clean()方法,获取DirectByteBuffer被分配的堆外内存地址,释放B在堆外内存开辟的空间,释放内存

        class DirectByteBuffer{
        	// Primary constructor
        	//
        	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;
      
      
      
        	}
                  ...
         private static class Deallocator
        	implements Runnable
        {
      
        	private static Unsafe unsafe = Unsafe.getUnsafe();
      
        	private long address;
        	private long size;
        	private int capacity;
      
        	private Deallocator(long address, long size, int capacity) {
        		assert (address != 0);
        		this.address = address;
        		this.size = size;
        		this.capacity = capacity;
        	}
      
        	public void run() {
        		if (address == 0) {
        			// Paranoia
        			return;
        		}
        		unsafe.freeMemory(address);
        		address = 0;
        		Bits.unreserveMemory(size, capacity);
        	}
      
        }
        ...
        }      
      

    Clean类,runnable这里对应Deallocator类型

    package sun.misc;
    
    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
    import java.security.AccessController;
    import java.security.PrivilegedAction;
    
    public class Cleaner extends PhantomReference<Object> {
    	private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    	private static Cleaner first = null;
    	private Cleaner next = null;
    	private Cleaner prev = null;
    	private final Runnable thunk;
    
    	private static synchronized Cleaner add(Cleaner var0) {
    		if(first != null) {
    			var0.next = first;
    			first.prev = var0;
    		}
    
    		first = var0;
    		return var0;
    	}
    
    	private static synchronized boolean remove(Cleaner var0) {
    		if(var0.next == var0) {
    			return false;
    		} else {
    			if(first == var0) {
    				if(var0.next != null) {
    					first = var0.next;
    				} else {
    					first = var0.prev;
    				}
    			}
    
    			if(var0.next != null) {
    				var0.next.prev = var0.prev;
    			}
    
    			if(var0.prev != null) {
    				var0.prev.next = var0.next;
    			}
    
    			var0.next = var0;
    			var0.prev = var0;
    			return true;
    		}
    	}
    
    	private Cleaner(Object var1, Runnable var2) {
    		super(var1, dummyQueue);
    		this.thunk = var2;
    	}
    
    	public static Cleaner create(Object var0, Runnable var1) {
    		return var1 == null?null:add(new Cleaner(var0, var1));
    	}
    
    	public void clean() {
    		if(remove(this)) {
    			try {
    				this.thunk.run();
    			} catch (final Throwable var2) {
    				AccessController.doPrivileged(new PrivilegedAction() {
    					public Void run() {
    						if(System.err != null) {
    							(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
    						}
    
    						System.exit(1);
    						return null;
    					}
    				});
    			}
    
    		}
    	}
    }
    

    当jvm回收掉DirectByteBuffer后,会将虚引用它的对象Cleaner入队ReferenceHandler中会消费的队列,同时ReferenceHandler线程发现新来了一个虚引用对象,会判断这个对象否为Cleaner,如果是,则会执行clean()方法,达到回收的目的

        /* 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<Object> r;
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        // The waiting on the lock may cause an OOME because it may try to allocate
                        // exception objects, so also catch OOME here to avoid silent exit of the
                        // reference handler thread.
                        //
                        // Explicitly define the order of the two exceptions we catch here
                        // when waiting for the lock.
                        //
                        // We do not want to try to potentially load the InterruptedException class
                        // (which would be done if this was its first use, and InterruptedException
                        // were checked first) in this situation.
                        //
                        // This may lead to the VM not ever trying to load the InterruptedException
                        // class again.
                        try {
                            try {
                                lock.wait();
                            } catch (OutOfMemoryError x) { }
                        } catch (InterruptedException x) { }
                        continue;
                    }
                }
    
                // Fast path for cleaners
                if (r instanceof Cleaner) {
                    ((Cleaner)r).clean();
                    continue;
                }
    
                ReferenceQueue<Object> q = r.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(r);
            }
        }
    }
  • 相关阅读:
    iap 详细
    血的教训,下次开工程 一点要写好判断空字符串方法
    iOS中的ScrollView
    自定义弹框加载方式
    CAGradientLayer简介(处理视图渐变色)
    iOS 制作view渐变的效果CAGradientLayer
    将vs2012的项目转化成VS2010
    关于Excel导入的HDR=YES; IMEX=1详解
    C#读取Excel表中的数据时,为何有些行的字段内容读取不到
    OLEDB读取EXCEL表格时,某些字段为空,怎么办?
  • 原文地址:https://www.cnblogs.com/windliu/p/9166364.html
Copyright © 2020-2023  润新知