【JAVA】【源码学习】Cleaner/Reference

简介

上一篇讲DirectByteBuffer时提到Cleaner用于释放内存,而Cleaner又跟Reference有关,那本篇就学习一下相关知识。

Cleaner

类注释很清楚的说明了,这个是一种轻量级的finalize机制(相对于VM调用而言),不管是内存还是其它资源都可以通过该机制释放。其机制主要是基于PhantomReference,这个一会讲到,先看下Clean而的源码:

public class Cleaner
    extends PhantomReference<Object>
{
    // Dummy reference queue, needed because the PhantomReference constructor
    // insists that we pass a queue.  Nothing will ever be placed on this queue
    // since the reference handler invokes cleaners explicitly.
    // 用于PhantomReference
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();

    // Doubly-linked list of live cleaners, which prevents the cleaners
    // themselves from being GC'd before their referents
    // 这一点比较关键,上面注释说的很清楚,防止cleaner对象本身比要清理的对象提前被GC
    static private Cleaner first = null;
	// 双向链表
    private Cleaner
        next = null,
        prev = null;
	
	// 链表ADD操作,线程保护
    private static synchronized Cleaner add(Cleaner cl) 
	
	// 链表REMOVE操作,线程保护
    private static synchronized boolean remove(Cleaner cl)

    private final Runnable thunk;

    private Cleaner(Object referent, Runnable thunk) 
	
	// ob: 需要清理的对象
	// 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) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null)
                            new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                        System.exit(1);
                        return null;
                    }});
        }
    }
}

整个代码比较简单,除了需要使用双向链表存储cleaner对象外,其它的就是封装。

PhantomReference

源码很简单,其中get函数直接返回null,也就是使用该类型的引用时,不管对象有没有被回收,都返回null。所以正常情况下应该用不着,典型场景就是Cleaner了。

public class PhantomReference<T> extends Reference<T> {
    // 直接返回null
    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

Reference

重头戏来了,Reference是WeakReference、SoftReference以及PhantomReference的基类,也是VM会操作的对象(所以reference才知道对象什么时候被回收啊),管理整个进程的reference对象,整个源码也很简洁,挑几个重点看下。

1 状态

类注释中讲了划分为几种状态,开始不理解,看完源码后,觉得应该是区分Reference的几种场景的,也就是区分类中几个变量在不同场景下的意义的,本身并没有一个类似状态变量的东西来确定当前Reference的状态。

  • Active:存活。垃圾回收器检测到对象可以被回收之后,如果有ReferenceQueue,则变为Pending状态,否则到Inactive状态。
  • Pending: 等待入队列 .
  • Queued:进入队列中
  • Inactive:去活,到这个状态后,状态不会再变更。

这样讲比较抽象,结合代码看,先看下相关变量的注释:

	/* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    volatile Reference next;

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
    transient private Reference<T> discovered;  /* used by VM */

next主要用于queued状态时,以便查找另外一个入队待处理的reference;而discovered则是在pending态下下一个待入队的reference对象。

2 清理线程

JAVA 的垃圾清理机制是用后台线程来回收内存,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);
            }
        });
    }
private static class ReferenceHandler extends Thread {
		...
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

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

可见进程内会初始化一个MAX_PRIORITY的后台线程来处理,最终会调用到tryHandlePending函数。

3 清理执行

tryHandlePending是最终进行清理的地方(Cleaner里的clean),以及队列维护.

static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
            	// 结合前面的状态来看。
                if (pending != null) {
                	// pending不为null,说明被VM的回收器检测到对象被回收了(是否finalize了不一定)
                    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状态下,discovered是指下一个pending的reference对象
                    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;
        }
		// clean调用后,进入队列
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

整个逻辑就是这样,VM负责通知Reference对象已经被回收了,Reference的清理线程在后台不停的工作,进行清理及列表维护。

ReferenceQueue

源码不贴了,逻辑比较简单,使用head维持一个链表。

WeakReference

看源码就是一个标记类,啥都没干,注释上有这么一句 Weak references are most often used to implement canonicalizing mappings,不太理解。

public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

SoftReference

也很简单,只不过多了clock和timestamp变量,clock由VM更新,timestamp也由VM使用。注释里有比较重要的说明

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError. Otherwise no constraints are placed upon the time at which a soft reference will be cleared or the order in which a set of such references to different objects will be cleared. Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.

这个很清楚的说明了VM保证在抛出OutOfMemoryError前会被清理,但是除此之外,何时被清理以及清理顺序是没有任何保证的。

public class SoftReference<T> extends Reference<T> {
    static private long clock;

    private long timestamp;

    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }

    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
}

总结

看完之后,cleaner机制是理解了,但是soft和weak又有点迷惑,我只能粗浅的理解,内存紧张的时候,作用差不多,够用的时候,soft存活时间稍微长点?感觉也不一定哦,没看到注释里说优先清理weak类型的啊,懵~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值