Java引用类型

Java引用类型

  • 应该说,引用对于对象来说是至关重要的,对象生来是要被用的,没有引用的对象自然就用不到了,也就面临被回收,判断对象存活的关键在于引用
引用的分类
  • JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用;JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用,终结器引用五种(引用强度逐渐减弱)。垃圾回收器对于不同引用强度的引用有不同的回收策略
    • 对象当然可以对应多种类型的引用,每个引用都会发挥其对应的作用(比如与各种队列的关联),但是在回收策略上由强度最高的引用决定
强引用
  • 强引用是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出 OOM 错误,使程序终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
  • 对于强引用来说,唯一使得其实例主动被回收的方法就是向强引用赋值null
    • 为强引用赋值null以促成JVM垃圾回收的最典型的案例就是ArrayList的clear方法
      public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }

      • 释放数组成员的内存,但是不释放数组的内存,因为后续可能还会使用,只是清除存储的对象数据,但是不回收已经分配的数组空间
  • 与其他几种引用类型相比,强引用没有对应的类型定义,也没有引用队列使用,毕竟是古老的默认的引用,同时作为最强的引用也没必要有这些
软引用
  • 如果一个对象只具有软引用,则类似于非必须生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存,可加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生
  • 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中
    public class SoftReferenceDemo {public static void main(String[] args) { Obj obj = new Obj(); SoftReference sr = new SoftReference(obj); obj = null; System.out.println(sr.get()); } }class Obj {int[] obj; public Obj() { obj = new int[1000]; } }

    • 使用SoftReference描述软引用
  • 使用软引用的一个例子:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题:使用软引用维系内存中的图片数据,如果空间够用就正常使用,如果内存不够了,垃圾回收器会回收内存中图片数据占用的空间
弱引用
  • 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
  • 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,JVM就会把这个弱引用加入到与之关联的引用队列中
    public class WeakReferenceDemo {public static void main(String[] args) {WeakReference sr = new WeakReference<>(new String("hello")); System.out.println(sr.get()); // hello System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); // null 对饮的实例已经被回收 } }

    • 使用WeakReference描述弱引用
ThreadLocal
  • 关于经典的ThreadLocal + 弱引用导致的内存泄漏在ThreadLocal原理的文章中介绍
虚引用
  • 虚引用主要用来跟踪对象被垃圾回收的活动,给程序以指示以在对象的内存被回收前采取必要的行动。或者说一个对象持有虚引用,实际上就是等待被回收的对象
  • 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用,并且无法通过该引用来获取对象
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
    public class PhantomReferenceDemo {public static void main(String[] args) { ReferenceQueue queue = new ReferenceQueue(); // 虚引用必须指定一个ReferenceQueue PhantomReference pr = new PhantomReference(new String("hello"), queue); System.out.println(pr.get()); // null } }

    • 使用PhantomReference描述虚引用
  • 关于虚引用的一个经典的使用案例是DirectByteBuffer类中定义的Cleaner类(继承自PhantomReference),Cleaner类可以借用虚引用的特性保证DirectByteBuffer实例被回收后,也可以同步回收由此类分配的堆外内存
DirectByteBuffer
  • DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现
    protected static final Unsafe unsafe = Bits.unsafe(); DirectByteBuffer(int cap) {// package-privatesuper(-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对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起同步被释放 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }

  • 如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放的?----虚引用的典型应用
    // Cleaner继承虚引用 public class Cleaner extends PhantomReference {// 虚引用必须结合虚引用队列使用 private static final ReferenceQueue dummyQueue = new ReferenceQueue(); // ....// DirectByteBuffer类初始化Cleaner类的语句 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); // Cleaner类提供的初始化方法 public static Cleaner create(Object var0, Runnable var1) { return var1 == null ? null : add(new Cleaner(var0, var1)); }// var1是DirectByteBuffer的引用,var2是Deallocator实例 private Cleaner(Object var1, Runnable var2) { // 传递给虚引用的构造函数,至此,DirectByteBuffer拥有两个类型的引用,一个是调用DirectByteBuffer类的上层类中持有的强引用,一个是Cleaner中持有(本质上是被其父类的父类Reference持有)的虚引用 super(var1, dummyQueue); this.thunk = var2; }// Cleaner内部维护了一个简单的链表 private static synchronized Cleaner add(Cleaner var0) { if (first != null) { var0.next = first; first.prev = var0; }first = var0; return var0; }public void clean() { if (remove(this)) { try { this.thunk.run(); } catch (final Throwable var2) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { if (System.err != null) { (new Error("Cleaner terminated abnormally", var2)).printStackTrace(); }System.exit(1); return null; } }); } } }// ...}// 执行堆外内存的回收的线程任务 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; }// 在clean方法中被调用 public void run() { if (address == 0) { // Paranoia return; } // 释放内存 unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); } }
    • Cleaner继承PhantomReference,PhantomReference必须与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能
    • 当某个被Cleaner引用的对象(DirectByteBuffer)将被回收时(DirectByteBuffer的强引用已经无效,只剩下虚引用,这个虚引用的类型其实就是Cleaner(继承自PhantomReference)),JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表(不是dummyQueue,pending列表是GC或者说是JVM维护的,pending列表中的非Cleaner的引用才会放入到对应的引用队列中)中,等待Reference Handler进行相关处理。其中,Reference Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作
      • Reference Handler的处理过程参考下边的Reference类的解析
    Java引用类型
    文章图片

    终结器引用
    • 终结器引用FinalReference,与前边几种引用一样都是属于Reference这个抽象类的子类,其作用在于其唯一子类Finalizer在类加载时创建了执行finalize方法的线程,用于在GC回收前执行回收方法
      final class Finalizer extends FinalReference { private static class FinalizerThread extends Thread { private volatile boolean running; FinalizerThread(ThreadGroup g) { super(g, null, "Finalizer", 0, false); } public void run() { // in case of recursive call to run() if (running) return; // Finalizer thread starts before System.initializeSystemClass // is called.Wait until JavaLangAccess is available while (VM.initLevel() == 0) { // delay until VM completes initialization try { VM.awaitInitLevel(1); } catch (InterruptedException x) { // ignore and continue } } final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; for (; ; ) { try { // 从队列中取出引用执行finalize方法 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); finalizer.setDaemon(true); finalizer.start(); } // ... }
    • 在可达性分析算法中,不可达对象默认的被JVM赋予FinalReference引用(实际上是Finalizer类型)并投喂到对应的一个ReferenceQueue中,等待Finalizer线程去执行此队列中的对象的finalize方法(在finalizer线程中,判断是否需要执行finalize方法)
    • Reference类
      • 除了强引用之外的其他四种引用实际上都以Reference类作为父类,而该类中定义了引用分类的核心功能
      // Reference类 static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); // 创建高优先级的Reference Handler线程 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); } }); } /* High-priority thread to enqueue pending References高优先级的守护线程用来处理GC向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 { // pre-load and initialize InterruptedException and Cleaner classes // so that we don't get into trouble later in the run loop if there's // memory shortage while loading/initializing them lazily. ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); }ReferenceHandler(ThreadGroup g, String name) { super(g, name); }public void run() { while (true) { tryHandlePending(true); } } }// 此属性承载着pending list中的下一个pending的reference,由JVM维护 private static Reference pending = null; // 高优先级的引用处理线程会死循环的执行此方法 static boolean tryHandlePending(boolean waitForNotify) { Reference 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) { // 等待pending list有reference后被JVM唤醒 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) { // 如果当前处理的reference是Cleaner类型的则执行clean方法 c.clean(); return true; }// 如果当前处理的reference不是Cleaner类型则加入到queue中 ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }Reference(T referent, ReferenceQueue queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }// 下边补充软引用与弱引用的实现 public class SoftReference extends Reference {/** * Timestamp clock, updated by the garbage collector 由GC去更新时钟 */ static private long clock; /** * Timestamp updated by each invocation of the get method.The VM may use * this field when selecting soft references to be cleared, but it is not * required to do so. 维护一个时间戳,类似于lru,每次调用get方法都会更新此时间戳,JVM可以以这个时间戳为凭证,判断内存不够时选择性的回收那些不常使用的弱引用 */ private long timestamp; /** * Creates a new soft reference that refers to the given object.The new * reference is not registered with any queue. * * @param referent object the new soft reference will refer to */ public SoftReference(T referent) { super(referent); this.timestamp = clock; }/** * Creates a new soft reference that refers to the given object and is * registered with the given queue. * * @param referent object the new soft reference will refer to * @param q the queue with which the reference is to be registered, *or null if registration is not required * */ public SoftReference(T referent, ReferenceQueue q) { super(referent, q); this.timestamp = clock; }/** * Returns this reference object's referent.If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns null. * * @returnThe object to which this reference refers, or *null if this reference object has been cleared */ public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; }}// 弱引用 public class WeakReference extends Reference {/** * Creates a new weak reference that refers to the given object.The new * reference is not registered with any queue. * * @param referent object the new weak reference will refer to */ public WeakReference(T referent) { super(referent); }/** * Creates a new weak reference that refers to the given object and is * registered with the given queue. * * @param referent object the new weak reference will refer to * @param q the queue with which the reference is to be registered, *or null if registration is not required */ public WeakReference(T referent, ReferenceQueue q) { super(referent, q); }}
      • Reference代码中包含的逻辑并非是四种引用的特性生效的全部逻辑,是需要JVM与GC配合使用的(逻辑不可见,只能从注释中瞥见一二),包括四种引用类型的子类的代码中也有对应的逻辑,大概的逻辑应该可以用下图去理解
        Java引用类型
        文章图片

      引用分类的作用
      • 【Java引用类型】类似于Java并发编程中性能提升手段组要是锁的类型细化,引用的分类也是为了便于对引用实例进行管理以提升性能
        • 程序员自己可以手动的设置一个引用的引用类型,这使得程序员也可以使用引用介入对象生命周期的管理
        • 更有利于JVM进行垃圾回收

        推荐阅读