侧边栏壁纸
博主头像
林雷博主等级

斜月沉沉藏海雾,碣石潇湘无限路

  • 累计撰写 132 篇文章
  • 累计创建 47 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

2、Java引用深入研究

林雷
2023-08-25 / 0 评论 / 0 点赞 / 198 阅读 / 7,718 字

20220830

一 Java引用深入研究

在Java中,使用Reference抽象类表示一个引用,Reference中的很多内容都是VM来调用的,比如GC清理了WeakReference后调用对象入队的逻辑等。我们先看一下Reference的继承结构:
Reference-Source-1-1

  • PhantomReference:幽灵引用,任何时候引用都可能会被清理,get获取引用的对象永远为null
  • SoftReference:软引用,当没有强引用时,并且内存不足时下一次GC会被清理
  • WeakReference:弱引用,当没有强引用时,下一次GC一定会被清理

三个引用都可以关联一个引用队列,表示当引用对象被回收时将Reference放入引用队列中;其中SoftReference、WeakReference可以不关联引用队列,PhantomReference必须关联一个引用队列。

1.1 软引用和弱引用

SoftReference、WeakReference分别表示软引用和弱引用,这两个引用在源码上几乎没有内容,如下:

/**
 * 软引用, 当内存空间充足时不会回收关联的对象, 但是内存空间不足时, 下一次GC会回收引用(没有强引用指向)
 */
public class SoftReference<T> extends Reference<T> {
    /** 时间戳, 是在GC中更新的 */
    static private long clock;

    /** 时间戳, 在每次调用{@link  #get()}方法时更新 */
    private long timestamp;

    /**
     * 构造函数
     * @param referent 关联的对象引用
     */
    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }

    /**
     * 构造函数
     * @param referent 关联的对象引用
     * @param q 关联的引用队列
     */
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

    /**
     * 获取关联的对象的引用, 可能会返回{@code null}
     * @return 关联的对象引用
     */
    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }

}
/**
 * 弱引用。不关心内存充不充足, 当没有强引用指向{@code T}时, 下一次GC一定会回收这个对象
 */
public class WeakReference<T> extends Reference<T> {
    /**
     * 构造函数
     * @param referent 关联的对象引用
     */
    public WeakReference(T referent) {
        super(referent);
    }

    /**
     * 构造函数
     * @param referent 关联的对象引用
     * @param q 关联的引用队列
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

这两个引用源码上没有内容,其主要的操作还是在其基类Reference上。

1.2 幽灵引用

幽灵引用PhantomReference,在构造时必须关联一个引用队列。源码如下:

/**
 * 幽灵(虚)引用, 任何时候都可能被回收
 */
public class PhantomReference<T> extends Reference<T> {
    /**
     * 获取引用对象, 这里直接返回{@code null}
     * @return {@code null}
     */
    public T get() {
        return null;
    }

    /**
     * 构造函数
     * @param referent 引用对象
     * @param q 队列
     */
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

PhantomReference有一个子类Cleaner,表示清理器,在做清理动作时可以关联一个Runnable对象的任务,Cleaner代码如下:

/**
 * 清理器, 是一个幽灵引用。内部是一个链表结构
 */
public class Cleaner extends PhantomReference<Object> {
    /** 关联的队列 */
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();

    /** 链表头 */
    static private Cleaner first = null;

    /** next与prev指针 */
    private Cleaner next = null, prev = null;

    /**
     * 添加清理器
     * @param cl 清理器
     * @return Cleaner
     */
    private static synchronized Cleaner add(Cleaner cl) {
        if (first != null) {
            cl.next = first;
            first.prev = cl;
        }
        first = cl;
        return cl;
    }

    /**
     * 移除清理器
     * @param cl 待移除的清理器
     * @return true/false
     */
    private static synchronized boolean remove(Cleaner cl) {
        //已经被清理, 不做任何操作 Start
        if (cl.next == cl)
            return false;
        //已经被清理, 不做任何操作 End

        //首节点 Start
        if (first == cl) {
            if (cl.next != null)
                first = cl.next;
            else
                first = cl.prev;
        }
        //首节点 End

        //更新指针 Start
        if (cl.next != null)
            cl.next.prev = cl.prev;
        if (cl.prev != null)
            cl.prev.next = cl.next;
        //更新指针 End

        //自引用 Start
        cl.next = cl;
        cl.prev = cl;
        //自引用 End
        return true;

    }

    /** 关联的清理任务 */
    private final Runnable thunk;

    /**
     * 创建清理器
     * @param referent 对应的引用
     * @param thunk 关联的清理任务
     */
    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue);
        this.thunk = thunk;
    }

    /**
     * 创建清理器
     * @param ob 对应的引用
     * @param thunk 关联的清理任务
     * @return 清理器
     */
    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;
                }
            });
        }
    }

}

其中clean() 方法主要就是执行关联的任务的run() 方法。

1.3 Reference

Reference是所有上述引用的基类,是一个抽象类。在Reference定义了引用入队的流程。在Reference中关联了用户的引用对象以及一个引用队列:

/** 关联的对象引用 */
private T referent;

/** 关联的引用队列 */
volatile ReferenceQueue<? super T> queue;

Reference本身是一个链表,具有next指针:

/**
 * 下一个元素, 当对象是
 * active: NULL
 * pending: this
 * Enqueued: 队列中下一个引用
 * Inactive: this
 */
@SuppressWarnings("rawtypes")
volatile Reference next;

/**
 * VM中使用的, 当VM发现有被回收的引用时存入. 操作时必须使用锁
 * 表示下一次要放入队列的引用
 */
transient private Reference<T> discovered;

/** 等待进入队列的引用列表 */
private static Reference<Object> pending = null;

这三个参数分别表示:

  • next:下一个指针】
  • discovered:被探索到的引用,表示这个引用内存空间已经被回收了,需要放入引用队列中的Reference
  • pending:可以理解为Reference的链表的首部,表示等待队列的Reference链表
    在操作上述的内容时,需要加锁:
/**
 * 锁对象, 用于与垃圾回收器同步
 */
static private class Lock {
}

/** 锁对象 */
private static Lock lock = new Lock();

1.3.1 构造函数

Reference有两个构造函数:

/**
 * 构造函数
 * @param referent 引用对象
 */
Reference(T referent) {
    this(referent, null);
}

/**
 * 构造函数
 * @param referent 引用对象
 * @param queue 引用队列
 */
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

1.3.2 引用处理器线程

在Reference中有一个线程ReferenceHandler,是在创建Reference静态代码块中就启动了这个线程,静态代码块如下:

static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent())
        ;
    //启动ReferenceHandler线程 Start
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
    handler.setPriority(Thread.MAX_PRIORITY);//高优先级
    handler.setDaemon(true);
    handler.start();
    //启动ReferenceHandler线程 End

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

ReferenceHandler代码如下:

/**
 * 将挂起的引用放入队列的高优先级线程, 这个线程是一直清理{@link #pending}链表中的引用
 */
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);
        }
    }
}

当ReferenceHandler线程启动后,就一直在运行,不停的调用tryHandlePending(boolean) 方法:

/**
 * 尝试清理被挂起的引用
 * @param waitForNotify 是否等待通知
 * @return true/false
 */
static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            if (pending != null) {//有pending的引用
                r = pending;
                c = r instanceof Cleaner ? (Cleaner) r : null;//Cleaner
                //改变指针
                pending = r.discovered;
                r.discovered = null;
            } else {//没有pending的引用
                if (waitForNotify) {//等待
                    lock.wait();
                }
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {//OOM错误
        Thread.yield();//让出CPU时间
        return true;//retry
    } catch (InterruptedException x) {
        return true;//retry
    }

    //执行清理器清理程序 Start
    if (c != null) {
        c.clean();//执行清理动作
        return true;
    }
    //执行清理器清理程序 End

    //放入引用队列 Start
    ReferenceQueue<? super Object> q = r.queue;
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    //放入引用队列 End
    return true;
}

线程的主要逻辑事一直在处理pending链表的数据,如果链表是Cleaner对象,则会调用其clean() 方法,而Cleaner.clean() 方法我们在上面介绍过了。并且将对应的Reference对象放到关联的ReferenceQueue队列中。

1.3.3 其他操作

Reference还有很多其他操作,主要有:

  • T get():获取关联的对象
  • void clear():清理引用对象,即将refenrent置为null。VM中会调用此方法
  • boolean isEnqueued():判断当前的引用是否已放入队列
  • boolean enqueue():将Reference对象(this)存放到关联的队列中,即调用ReferenceQueue.enqueue(Reference) 方法。

引用队列 ReferenceQueue.enqueue(Reference) 代码如下:

/**
 * 入队, 只会在Reference class中调用
 * @param r 引用
 * @return true/false
 */
boolean enqueue(Reference<? extends T> r) {
    synchronized (lock) {
        //为空或已入队, 返回false Start
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        //为空或已入队, 返回false End

        assert queue == this;
        r.queue = ENQUEUED;//已入队
        r.next = (head == null) ? r : head;//更新next指针
        head = r;//更新head指针
        queueLength++;//队列长度自增
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();//唤醒所有等待的线程
        return true;
    }
}

放入之后Reference关联的queue就会变成ENQUEUED对象,所以上述isEnqueued() 判断是否已入队的逻辑就是雨ENQUEUED 比较即可。
然后调用lock.notifyAll() 唤醒被阻塞的线程,例如用户的poll() 线程。引用队列的其他逻辑也很简单,不做详细介绍了。

1.4 小结

在Java中有强引用、软引用、弱引用、幽灵引用,其中强引用就是我们在程序中使用new等关键字创建的引用。

  • SoftReference:软引用,内存充足时不会回收,内存不足并且没有强引用关联时会在下一次GC回收软引用中的对象
  • WeakReference:弱引用,与内存充不充足无关,当没有强引用关联时下一次GC一定会回收弱引用中的对象
  • PhantomReference:幽灵引用,又称为虚引用,幽灵引用于弱引用类似,当没有强引用关联时下一次GC一定会回收。与弱引用不同的时,幽灵引用在使用时必须与引用队列关联,并且其get() 方法一定返回null,所以就跟没有引用是一样的,永远无法获取到存放的值
  • Reference:是上述三大引用的基类,是一个抽象类,内部是一个单向链表结构。当VM在回收引用的内存时(SoftReference、WeakReference、PhantomReference),会将被回收的引用放到链表中;而在Reference内部关联了一个高优先级的线程ReferenceHandler,这个线程一直在扫描被回收的Reference链表,如果有数据则将其放入引用队列中(如果存在引用队列)
  • ReferenceQueue:引用队列,其实内部并不会存储Reference对象,而是利用Reference的链表结构存放
0

评论区