1.8、JDK 源码分析 - Reference 框架 (二)

原创
2022/07/08 14:07
阅读数 330

摘要

上一篇博文讲解了 JDK-引用框架的顶层引用类Reference的相关操作源码,本篇文章将继续前面的知识,对Reference相关子类进行讲解;

引用类型

类型 对应类 特征
强引用   强引用的对象绝对不会被gc回收
软引用 SoftReference 如果物理内存充足则不会被gc回收,如果物理内存不充足则会被gc回收。
弱引用 WeakReference 一旦被gc扫描到则会被回收
虚引用 PhantomReference 不会影响对象的生命周期,形同于无,任何时候都可能被gc回收
  FinalReference 用于收尾机制(finalization)

一、强引用

该类引用没有具体的实现类;具体相关说明请参照上一篇博文;

二、软引用(SoftReference)

在JAVA中,由SoftReference来实现软引用。

系统将要发生内存溢出(oom)之前,会回收软引用的对象,如果回收后还没有足够的内存,抛出内存溢出异常;

使用SoftReference类,将要软引用的对象作为参数传入;如果构造方法传入了ReferenceQueue队列,如果引用的对象被回收,则将其加入该队列。

软引用对象,垃圾收集器根据内存需求酌情清除这些对象。 软引用最常用于实现内存敏感的缓存。

假设垃圾收集器在某个时间点确定一个对象为软可达。 这时,它可以选择以原子方式清除对该对象的所有软引用,以及对通过强引用链可访问该对象的任何其他软可达对象的所有软引用。 在同一时间或稍后的某个时间,它将把那些注册在引用队列中的新清除的软引用排队。

保证在虚拟机抛出OutOfMemoryError之前,对软可达对象的所有软引用都已被清除。 否则,不会对清除软引用的时间或清除一组对不同对象的引用的顺序施加限制。 但是,鼓励虚拟机实现不清除最近创建或最近使用的软引用。

这个类的直接实例可以用来实现简单的缓存; 这个类或派生的子类也可以用于更大的数据结构中,以实现更复杂的缓存。 软引用只要是强可达的,即实际上是在使用中的,即软引用不会被清除。 因此,例如,一个复杂的缓存可以通过保持对这些条目的强引用来防止其最近使用的条目被丢弃,而剩下的条目则由垃圾收集器决定丢弃。

public class SoftReference<T> extends Reference<T> {
    /**
     * Timestamp clock, updated by the garbage collector
     */
    static private long clock;
    /**
     * 每次调用get方法时更新的时间戳。 虚拟机在选择要清除的软引用时,可能会使用此字段,但不需要这样做。
     */
    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;
    }

    /**
     * 返回此引用对象的引用。 如果该引用对象已被程序或垃圾收集器清除,则此方法返回<code>null</code>。
     *
     */
    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
}

使用案例:

    ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
    String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

    str = null;
    // Notify GC
    System.gc();

    System.out.println(softReference.get()); // abc

    Reference<? extends String> reference = referenceQueue.poll();
    System.out.println(reference); //null

三、弱引用(WeakReference)

弱引用对象,不阻止其引用-可被终结、终结和回收。弱引用最常用于实现规范化映射。

假设垃圾收集器在某个时间点确定一个对象是弱可达。 那时,它将原子地清除对该对象的所有弱引用,以及对通过强引用和软引用链可访问该对象的任何其他弱可及对象的所有弱引用。 同时,它将声明所有以前弱可及的对象都是可终结的。 在同一时间或稍后的某个时间,它将把那些新清除的弱引用放入引用队列中

public class WeakReference<T> extends Reference<T> {

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


具体应用可以查看ThreadLocal.ThreadLocalMap类中的Entry类,该类实现弱引用用于存放key。读者如果对ThreadLocal相关知识感兴趣,可以查看JDK源码分析的ThreadLocal这篇博问。另外,JDK中还有WeakHashMap、WeakHashtable等内部都使用了弱引用相关的知识内容;

四、虚引用(PhantomReference)

虚引用又称幻像引用,在收集器确定它们的引用可能被回收后进入队列。 幻影引用通常用于以比Java终结机制更灵活的方式调度事前清理操作。

如果垃圾收集器在某个时间点确定虚引用的引用是虚可达,那么在那个时间点或稍后的某个时间点,它将进入该引用的队列。为了确保可回收的对象保持不变,虚引用的引用可能不会被检索:虚引用的get方法总是返回null。

与软引用和弱引用不同,虚引用在进入队列时不会被垃圾收集器自动清除。 通过虚引用可访问的对象将保持不变,直到所有此类引用被清除或它们本身变得不可访问。可以用null队列创建一个虚引用,但是这样的引用是完全无用的:它的get方法总是返回null,而且,由于它没有队列,它永远不会被加入队列。

public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

为一个对象设置PhantomReference唯一的目的就是能在这个对象被收集器回收时收到一个系统通知。

使用场景

在NIO中可以使用堆外的内存,也叫直接内存,直接内存的回收就是使用的虚引用通知机制。

可以使用ByteBuffer的allocateDirect方法申请直接内存,具体代码如下:

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);

allocateDirect方法会创建一个DirectByteBuffer对象,下面就是构造方法:

  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;
    }


在构造方法中,可以看到Cleaner对象的创建,Cleaner类的父类就是PhantomReference,很明显这个对象就是执行清理工作的。详细查看Cleaner的源码前,先看Deallocator类,这个类就是一个Runnable,在run方法中通过Unsafe类的freeMemory方法释放内存。具体代码如下:

 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);
        }
    }

Cleaner类

上面有提到该类是PhantomReference的一个子类;主要用于一些自定义清理动作;上面有提到Java NIO中可使用直接内存,它的回收就是使用该类来来完成; 我们在之前分析Reference的源码时,已经了解到当一个引用处于pending状态,ReferenceHandle在处理它的时候,如果发现它是Cleaner对象,则会直接调用clean方法;

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<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }
                        System.exit(1);
                        return null;
                    }
                });
            }
        }
    }
}


五、FinalReference

最终引用,用于实现终结;该类只有一个构造方法,并且必传RQ的;FinalReference访问权限为package,并且只有一个子类Finalizer,同时Finalizer 是final修饰的类,所以无法继承扩展。

package java.lang.ref;
/**
 * Final references, used to implement finalization
 */
class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

Finalizer子类

与Finalizer相关联的是Object中的finalize()方法,在类加载的过程中,如果当前类有覆写finalize()方法,则其对象会被标记为finalizer类,这种类型的对象被回收前会先调用其finalize()。

具体的实现机制是,在gc进行可达性分析的时候,如果当前对象是finalizer类型的对象,并且本身不可达(与GC Roots无相连接的引用),则会被加入到一个ReferenceQueue类型的队列(F-Queue)中。而系统在初始化的过程中,会启动一个FinalizerThread实例的守护线程(线程名Finalizer),该线程会不断消费F-Queue中的对象,并执行其finalize()方法(runFinalizer),并且runFinalizer方法会捕获Throwable级别的异常,也就是说finalize()方法的异常不会导致FinalizerThread运行中断退出。对象在执行finalize()方法后,只是断开了与Finalizer的关联,并不意味着会立即被回收,还是要等待下一次GC,而每个对象的finalize()方法都只会执行一次,不会重复执行。

从源码可以了解到:当GC分析finalizer类型的对象不可达时,JVM调用Finalizer.register将一个finalizer类对象放入F-Queue中;

源码如下:

final class Finalizer extends FinalReference<Object> {
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private static Finalizer unfinalized = null;
    private static final Object lock = new Object();
    private Finalizer next = null, prev = null;

    private boolean hasBeenFinalized() {
        return (next == this);
    }

    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    private void remove() {
        synchronized (lock) {
            if (unfinalized == this) {
                if (this.next != null) {
                    unfinalized = this.next;
                } else {
                    unfinalized = this.prev;
                }
            }
            if (this.next != null) {
                this.next.prev = this.prev;
            }
            if (this.prev != null) {
                this.prev.next = this.next;
            }
            this.next = this;
            this.prev = this;
        }
    }

    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }

    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

    //执行finalize()方法
    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);
                /* 清除包含此变量的堆栈槽,以减少保守GC错误保留的机会*/
                finalizee = null;
            }
        } catch (Throwable x) {
        }
        super.clear();
    }

    /* 在系统线程组中为给定的Runnable创建一个优先级为中级终结器线程,并等待它完成。
     * runFinalization和runFinalizersOnExit都使用这个方法。 前一个方法调用所有pengding的终结器,
     *而如果启用了退出时终结器,则后者调用所有未调用的
     *终结器。这两个方法可以通过将它们的工作卸载给常规终结器线程并等待该线程完成来实现。 然而,创建新线程的优点是,
     *它将这些方法的调用程序与停滞或死锁的终结器线程隔离开来。
     */
    private static void forkSecondaryFinalizer(final Runnable proc) {
        AccessController.doPrivileged(
                new PrivilegedAction<Void>() {
                    public Void run() {
                        ThreadGroup tg = Thread.currentThread().getThreadGroup();
                        for (ThreadGroup tgn = tg;
                             tgn != null;
                             tg = tgn, tgn = tg.getParent())
                            ;
                        Thread sft = new Thread(tg, proc, "Secondary finalizer");
                        sft.start();
                        try {
                            sft.join();
                        } catch (InterruptedException x) {
                            /* Ignore */
                        }
                        return null;
                    }
                });
    }

    /* Called by Runtime.runFinalization() */
    static void runFinalization() {
        if (!VM.isBooted()) {
            return;
        }

        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;

            public void run() {
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (; ; ) {
                    Finalizer f = (Finalizer) queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
            }
        });
    }

    //Invoked by java.lang.Shutdown
    static void runAllFinalizers() {
        if (!VM.isBooted()) {
            return;
        }

        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;

            public void run() {
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (; ; ) {
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }
            }
        });
    }

    private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }

        public void run() {
            if (running)
                return;
            //终结器线程在System.initializeSystemClass被调用之前启动。 等待直到JavaLangAccess可用
            while (!VM.isBooted()) {
                // 延迟到虚拟机完成初始化
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            //重复调用queue处理Finalizer
            for (; ; ) {
                try {
                    Finalizer f = (Finalizer) queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;tgn != null; tg = tgn,tgn = tg.getParent()) ;
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2); //设置优先级为8
        finalizer.setDaemon(true);//设置为守护线程
        finalizer.start();
    }
}
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部