GC与Reference


引用类型

  • SoftReference

    普通的GC不会回收软引用,只有在即将发生OOM的时候(即最后一次Full GC),如果被引用的对象只有SoftReference指向的引用,才会被回收.

  • WeakReference

    当发生GC时,如果被引用的对象只有WeakReference指向的引用,就会被回收

  • PhantomReference

    不能通过虚引用获取到其关联的对象,但是当GC发生时,如果其引用的对象被回收,这个事件是可以感知的,这样就可以做相应的处理.

  • 强引用

GC是如何决定一个对象是否被回收?

从GC Root开始向下搜索,如果对象与GC之间存在引用链,则对象是可达的(reachable),GC会根据是否可达,以及对象的可达性来决定对象是否可以被回收.

而对象的可达性与引用类型相关:

  • 强可达
  • 软可达
  • 虚可达
  • 弱可达
  • 不可达

Refercence的几种状态

一个Reference对象可能处于以下4种状态中的一种:

  • Active

    新创建的对象都是处于这个状态;

    GC检测到引用的对象可达性改变时,会将状态转变成Pending或者Inactiive(如果refercence创建时带了referenceQueue,则会将refercence加入到pending 引用队列中)

    queue = 如果创建Refercence时注册了queue,则对应那个queue,如果没有,则为RefercenceQueue.NULL
    next = null
  • Pending

    马上要被放进队列中的对象,即即将被回收的对象

    pending引用队列中的一个元素,等着被ReferenceHandler线程(一个守护线程)去enqueue.

    没有注册(即创建时没有带queue的)的对象不会处于这个状态;

    queue = 注册时的RefercenceQueue
    next = this
  • Enqueued

    对象的内存已经被回收了,已经把这个对象放到了一个队列中

    当一个对象从它的引用队列中移除时,它就是Inactive状态了,未注册的对象不会处于这个状态;

    queue = RefercenceQueue.ENQUEUED
    next = queue中下一个对象,或者this(这是最后一个元素)
  • Inactive

    最终状态,不会再改变了.

    queue = RefercenceQueue.NULL
    next = this

处理流程

JVM在进行GC的时候,如果当前对象只被Reference对象引用,JVM会根据Reference的具体类型和堆内存的使用情况来决定:

是否把对应的Reference对象加入到一个由Reference构成的pending链表上(即Refercence中的next属性).

如果能加入到pending链表上,则JVM会同时通知ReferenceHandler线程进行处理.

ReferenceHandler线程是在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);
        handler.start();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

再来看看ReferenceHandler:

    private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }

    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) {
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

ReferenceHandler线程内部的run方法,会不断从pending链表中获取Refercence对象,

如果能获取到,则根据不同的引用类型进行处理;

如果获取不到,则调用wait方法等待GC回收对象,处理pending链表的通知.

参考文章

  1. http://ifeve.com/java-reference%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/

文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
Glide原理分析 Glide原理分析
一个图片加载库应该具备的功能 图片下载 各种格式图片编解码 图片显示 缓存 图像处理:圆角,色调,调整大小等等 现在分析下Glide是如何实现这个图片加载库的,先来看一下Glide的主要模型 Glide内部模型TargetGlide可以将
2020-11-03
下一篇 
leakcanary是如何捕获内存泄漏的 leakcanary是如何捕获内存泄漏的
Java中第三方应用如果想判断是否存在内存泄漏,一般都会利用WeakReference + RefercenceQueue的机制去判定. 因为GC之后,理论上会回收掉WeakRefercence对象的内存(如果不存在其他到GCRoot的路径
2020-10-31
  目录