为了知足对不一样状况的垃圾回收需求,从Java从版本1.2开始,引入了4种引用类型(实际上是额外增长了三种)的概念。本文将详细介绍这四种引用。html
Java中的4中引用类型分别为强引用(String Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)。java
> - 强引用:Java中的引用,默认都是强引用。好比new一个对象,对它的引用就是强引用。对于被强引用指向的对象,就算JVM内存不足OOM,也不会去回收它们。 > - 软引用:若一个对象只被软引用所引用,那么它将在JVM内存不足的时候被回收,即若是JVM内存足够,则软引用所指向的对象不会被垃圾回收(其实这个说法也不够准确,具体缘由后面再说)。根据这个性质,软引用很适合作内存缓存:既能提升查询效率,也不会形成内存泄漏。 > - 弱引用:若一个对象只被弱引用所引用,那么它将在下一次GC中被回收掉。如ThreadLocal和WeakHashMap中都使用了弱引用,防止内存泄漏。 > - 虚引用:虚引用是四种引用中最弱的一种引用。咱们永远没法从虚引用中拿到对象,被虚引用引用的对象就跟不存在同样。虚引用通常用来跟踪垃圾回收状况,或者能够完成垃圾收集器以外的一些定制化操做。Java NIO中的堆外内存(DirectByteBuffer)由于不受GC的管理,这些内存的清理就是经过虚引用来完成的。缓存
引用队列(Reference Queue)是一个链表,顾名思义,存放的是引用对象(Reference对象)的队列。 软引用与弱引用能够和一个引用队列配合使用,当引用所指向的对象被垃圾回收以后,该引用对象自己会被添加到与之关联的引用队列中,从而方便后续一些跟踪或者额外的清理操做。 由于没法从虚引用中拿到目标对象,虚引用必须和一个引用队列配合使用。ide
设置JVM的启动参数为 > -Xms10m -Xmx10mthis
public class ReferenceTest { private static int _1MB = 1024 * 1024; private static int _1KB = 1024; public static void main(String[] args) throws InterruptedException { // 引用队列,存放Reference对象 ReferenceQueue queue = new ReferenceQueue(); // 定义四种引用对象,强/弱/虚引用为1kb,软引用为1mb Byte[] strong = new Byte[_1KB]; SoftReference<byte[]> soft = new SoftReference<>(new Byte[_1MB], queue); WeakReference<byte[]> weak = new WeakReference<>(new Byte[_1KB], queue); PhantomReference<byte[]> phantom = new PhantomReference<>(new Byte[_1KB], queue); Reference<string> collectedReference; // 初始状态 System.out.println("Init: Strong Reference is " + strong); System.out.println("Init: Soft Reference is " + soft.get()); System.out.println("Init: Weak Reference is " + weak.get()); System.out.println("Init: Phantom Reference is " + phantom.get()); do { collectedReference = queue.poll(); System.out.println("Init: Reference In Queue is " + collectedReference); } while (collectedReference != null); System.out.println("********************"); // 第一次手动触发GC System.gc(); // 停100ms保证垃圾回收已经执行 Thread.sleep(100); System.out.println("After GC: Strong Reference is " + strong); System.out.println("After GC: Soft Reference is " + soft.get()); System.out.println("After GC: Weak Reference is " + weak.get()); System.out.println("After GC: Phantom Reference is " + phantom.get()); do { collectedReference = queue.poll(); System.out.println("After GC: Reference In Queue is " + collectedReference); } while (collectedReference != null); System.out.println("********************"); // 再分配1M的内存,以模拟OOM的状况 Byte[] newByte = new Byte[_1MB]; System.out.println("After OOM: Strong Reference is " + strong); System.out.println("After OOM: Soft Reference is " + soft.get()); System.out.println("After OOM: Weak Reference is " + weak.get()); System.out.println("After OOM: Phantom Reference is " + phantom.get()); do { collectedReference = queue.poll(); System.out.println("After OOM: Reference In Queue is " + collectedReference); } while (collectedReference != null); } }
上述代码的输出结果为:线程
Init: Strong Reference is [Ljava.lang.Byte;@74a14482 Init: Soft Reference is [Ljava.lang.Byte;@1540e19d Init: Weak Reference is [Ljava.lang.Byte;@677327b6 Init: Phantom Reference is null Init: Reference In Queue is null ******************** After GC: Strong Reference is [Ljava.lang.Byte;@74a14482 After GC: Soft Reference is [Ljava.lang.Byte;@1540e19d After GC: Weak Reference is null After GC: Phantom Reference is null After GC: Reference In Queue is java.lang.ref.WeakReference@14ae5a5 After GC: Reference In Queue is java.lang.ref.PhantomReference@7f31245a After GC: Reference In Queue is null ******************** After OOM: Strong Reference is [Ljava.lang.Byte;@74a14482 After OOM: Soft Reference is null After OOM: Weak Reference is null After OOM: Phantom Reference is null After OOM: Reference In Queue is java.lang.ref.SoftReference@6d6f6e28 After OOM: Reference In Queue is null
如下是引用类的UML图code
弱引用,软引用和虚引用都继承自Reference类,咱们从Reference类看起htm
// 此Reference对象可能会有四种状态:active, pending, enqueued, inactive // avtive: 新建立的对象状态是active // pending: 当Reference所指向的对象不可达,而且Reference与一个引用队列关联,那么垃圾收集器 // 会将Reference标记为pending,而且会将之加到pending队列里面 // enqueued: 当Reference从pending队列中,移到引用队列中以后,就是enqueued状态 // inactive: 若是Reference所指向的对象不可达,而且Reference没有与引用队列关联,Reference // 从引用队列移除以后,变为inactive状态。inactive就是最终状态 public abstract class Reference<t> { // 该对象就是Reference所指向的对象,垃圾收集器会对此对象作特殊处理。 private T referent; /* Treated specially by GC */ // Reference相关联的引用队列 volatile ReferenceQueue<!--? super T--> queue; // 当Reference是active时,next为null // 当该Reference处于引用队列中时,next指向队列中的下一个Reference // 其余状况next指向this,即本身 // 垃圾收集器只需判断next是否是为null,来看是否须要对此Reference作特殊处理 volatile Reference next; // 当Reference在pending队列中时,该值指向下一个队列中Reference对象 // 另外垃圾收集器在GC过程当中,也会用此对象作标记 transient private Reference<t> discovered; /* used by VM */ // 锁对象 static private class Lock { } private static Lock lock = new Lock(); // pending队列,这里的pending是pending链表的队首元素,通常与上面的discovered变量一块儿使用 private static Reference<object> pending = null; // 获取Reference指向的对象。默认返回referent对象 public T get() { return this.referent; } }
Reference类跟垃圾收集器紧密关联,其状态变化以下图所示:对象
上述步骤大多数都是由GC线程来完成,其中Pending到Enqueued是用户线程来作的。Reference类中定义了一个子类ReferenceHandler,专门用来处理Pending状态的Reference。咱们来看看它具体作了什么。blog
public abstract class Reference<t> { // 静态块,主要逻辑是启动ReferenceHandler线程 static { // 建立ReferenceHandler线程 ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); // 设置成守护线程,最高优先级,并启动 handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); // 访问控制 SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); } // 内部类ReferenceHandler,用来处理Pending状态的Reference 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); } } // 静态块,确保InterruptedException和Cleaner已经被ClassLoader加载 // 由于后面会用到这两个类 static { ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { // 死循环调用tryHandlePending方法 while (true) { tryHandlePending(true); } } } }
Reference类在加载进JVM的时候,会启动ReferenceHandler线程,并将它设成最高优先级的守护线程,不断循环调用tryHandlePending方法。 接下来看tryHandlePending方法:
// waitForNotify默认是true。 static boolean tryHandlePending(boolean waitForNotify) { Reference<object> r; Cleaner c; try { // 须要在同步块中进行 synchronized (lock) { // 判断pending队列是否为空,pending是队首元素 if (pending != null) { // 取到pending队列队首元素,赋值给r r = pending; // Cleaner类是Java NIO中专门用来清理堆外内存(DirectByteBufer)的类,这里对它作了特殊处理 // 当没有其余引用指向堆外内存时,与之关联的Cleaner会被加到pending队列中 // 若是该Reference是Cleaner实例,那么取到该Cleaner,后续能够作一些清理操做。 c = r instanceof Cleaner ? (Cleaner) r : null; // r.discovered就是下一个元素 // 如下操做即为将队首元素从pending队列移除 pending = r.discovered; r.discovered = null; } else { // 若是pending队列为空,则释放锁等待 // 当有Reference添加到pending队列中时,ReferenceHandler线程会今后处被唤醒 if (waitForNotify) { lock.wait(); } return waitForNotify; } } } catch (OutOfMemoryError x) { // OOM时,让出cpu Thread.yield(); return true; } catch (InterruptedException x) { return true; } // 给Cleaner的特殊处理,调用clean()方法,以释放与之关联的堆外内存 if (c != null) { c.clean(); return true; } // 此处,将此Reference加入到与之关联的引用队列 ReferenceQueue<!--? super Object--> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }
看到这里,豁然开朗。ReferenceHandler线程专门用来处理pending状态的Reference,跟GC线程组成相似生产者消费者的关系。当pending队列为空,则等待;当Reference关联的对象被回收,Reference被加入到pending队列中以后,ReferenceHandler线程会被唤醒来处理pending的Reference,主要作三件事:
引用队列比较简单,能够直接理解为一个存放Reference的链表,在此再也不费笔墨。
// 灰常简单,只重写了一个构造方法,一个get方法 public class PhantomReference<t> extends Reference<t> { // get方法永远返回null public T get() { return null; } // 只提供了一个包含ReferenceQueue的构造方法,说明它必须和引用队列一块儿使用 public PhantomReference(T referent, ReferenceQueue<!--? super T--> q) { super(referent, q); } }
通常状况下虚引用使用得比较少,最为人所熟知的就是PhantomReference的子类Cleaner了,它用来清理NIO中的堆外内存。有机会能够专门写篇文章来说讲它。
// 更加简单,只重写了两个构造方法 public class WeakReference<t> extends Reference<t> { public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<!--? super T--> q) { super(referent, q); } }
太过简单,不作额外讲解。
// 相比WeakReference,它增长了两个时间戳,clock和timestamp // 这两个参数是实现他们内存回收上区别的关键 public class SoftReference<t> extends Reference<t> { // 每次GC以后,若该引用指向的对象没有被回收,则垃圾收集器会将clock更新成当前时间 static private long clock; // 每次调用get方法的时候,会更新该时间戳为clock值 // 因此该值保存的是上一次(最近一次)GC的时间戳 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; } // 每次调用,更新timestamp的值,使之等于clock的值,即最近一次gc的时间 public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; } }
SoftReference除了多了两个时间戳以外,跟WeakReference几乎没有区别,它是如何作到在内存不足时被回收这件事的呢?其实这是垃圾收集器干的活。垃圾收集器回收SoftReference所指向的对象,会看两个维度:
而具体何时回收SoftReference所指向的对象呢,能够参考以下公式: > interval <= free_heap * ms_per_mb
其中interval为上一次GC与当前时间的差值,以毫秒为单位;free_heap为当前JVM中剩余的堆空间大小,以MB为单位;ms_per_mb能够理解为一个常数,即每兆空闲空间可维持的SoftReference的对象生存的时长,默认为1000,能够经过JVM参数*-XX:SoftRefLRUPolicyMSPerMB设置。 若是上述表达式返回true,则清理SoftReference所指向的对象,并将该SoftReference加入到pending*队列中;不然不作处理。因此说在JVM内存不足的时候回收软引用这个说法不是很是准确,只是个经验说法,软引用的回收,还跟它存活的时间有关,甚至跟JVM参数设置(-XX:SoftRefLRUPolicyMSPerMB)都有关系!
How Hotspot Clear Softreference</t></t></t></t></t></t></object></t></object></t></t></string></byte[]></byte[]></byte[]>