• Netty源码—七、内存释放


    Netty本身在内存分配上支持堆内存和直接内存,我们一般选用直接内存,这也是默认的配置。所以要理解Netty内存的释放我们得先看下直接内存的释放。

    Java直接内存释放

    我们先来看下直接内存是怎么使用的

    ByteBuffer.allocateDirect(capacity)
    

    申请的过程是其实就是创建一个DirectByteBuffer对象的过程,DirectByteBuffer对象只相当于一个holder,包含一个address,这个是直接内存的指针。

    • 调用native方法申请内存
    • 初始化cleaner
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
    
    DirectByteBuffer(int cap) {                   // package-private
        // 省略中间代码...
        // 创建一个cleaner,最后会调用Deallocator.run来释放内存
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }
    

    Cleaner这个类继承自PhantomReference,也就是所谓的虚引用,这种类型引用的特点是:

    • 使用get方法不能获取到对象
    • 只要引用的对象除了PhantomReference之外没有其他引用了,JVM随时可以将PhantomReference引用的对象回收。

    JVM在回前会将将要被回收的对象放在一个队列中,由于Cleaner继承自PhantomReference,队列的实现是使用cleaner的

    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
    

    这个队列在PhantomReference的父类Reference中使用到了,Reference这个类在初始化的时候会启动一个线程来调用cleaner.clean方法,在Reference的静态代码块中启动线程

    // java.lang.ref.Reference
    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);
        // 启动ReferenceHandler线程
        handler.start();
    	// 省略中间代码...
    }
    

    该线程的主要作用就是调用tryHandlePending

    // java.lang.ref.Reference#tryHandlePending
    static boolean tryHandlePending(boolean waitForNotify) {
            Reference<Object> r;
            Cleaner c;
            try {
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        // 'instanceof' might throw OutOfMemoryError sometimes
                        // so do this before un-linking 'r' from the 'pending' chain...
                        c = r instanceof Cleaner ? (Cleaner) r : null;
                        // unlink 'r' from 'pending' chain
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        // The waiting on the lock may cause an OutOfMemoryError
                        // because it may try to allocate exception objects.
                        if (waitForNotify) {
                            lock.wait();
                        }
                        // retry if waited
                        return waitForNotify;
                    }
                }
            } catch (OutOfMemoryError x) {
                // Give other threads CPU time so they hopefully drop some live references
                // and GC reclaims some space.
                // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
                // persistently throws OOME for some time...
                Thread.yield();
                // retry
                return true;
            } catch (InterruptedException x) {
                // retry
                return true;
            }
    
            // Fast path for cleaners
            if (c != null) {
                // 调用clean方法
                c.clean();
                return true;
            }
    
            ReferenceQueue<? super Object> q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
            return true;
    }
    

    System.gc不能回收堆外内存,但是会回收已经没有使用了DirectByteBuffer对象,该对象被回收的时候会将cleaner对象放入队列中,在Reference的线程中调用clean方法来回收堆外内存 。cleaner.run执行的是传入参数的thunk.run方法,这里thunk是Deallocator,所以最后执行的Deallocator.run方法

    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        // 释放内存
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
    

    所以最后通过unsafe.freeMemory释放了申请到的内存。

    总结一下,在申请内存的时候调用的是java.nio.ByteBuffer#allocateDirect

    会new DirectByteBuffer,通过Cleaner.create创建Cleaner,同时传入Deallocator作为Runnable参数,在Cleaner.clean的时候会调用该Deallocator.run来处理

    Cleaner继承自PhantomReference,包含一个ReferenceQueue,在DirectByteBuffer不再使用的时候,该对象是处于Java堆的,除了该PhantomReference引用了DirectByteBuffer外,没有其他引用的时候,jvm会把cleaner对象放入ReferenceQueue队列中。

    PhantomReference继承了Reference,Reference会启动一个线程(java.lang.ref.Reference.ReferenceHandler#run)去调用队列中的cleaner.clean方法。

    Netty内存释放

    Netty使用的直接内存的释放方式和JDK的释放方式略有不同。Netty开始释放内存的时候是调用free方法的时候

    io.netty.buffer.PoolArena#free
    io.netty.buffer.PoolArena.DirectArena#destroyChunk
    

    最终释放内存的方法有两种

    1. 利用反射获取unsafe,调用Unsafe#freeMemory
    2. 利用反射获取DirectByteBuffer#cleaner,通过反射调用cleaner.clean方法

    两种不同的方式依赖的条件不同,使用场景也不同

    使用反射调用cleaner.clean

    要满足以下条件之一的时候使用这种方式

    1. 没有可使用的直接内存
    2. 不能获取unsafe
    3. directBuffer没有传入long、int的构造方法

    使用unsafe

    不能使用上面这种方式的都使用unsafe

  • 相关阅读:
    读jQuery之十六(事件代理)
    双向列表(JS)
    单向链表(JS)
    子程序(过程、函数、方法)
    jQuery(1.6.3) 中css方法对浮动的实现缺陷
    操作class属性的新APIclassList
    ajax后退解决方案(四)
    设置元素浮动的几种方式
    各浏览器中使用getAttribute获取checkbox/radio的checked值不同
    IE6/7不支持hashchange事件
  • 原文地址:https://www.cnblogs.com/sunshine-2015/p/9393410.html
Copyright © 2020-2023  润新知