在阅读本文之前最好对 Reference 框架有一个整体的把握, 可以参考我上一篇博客 Reference 框架概览 ; 本文主要讲了 Reference 的子类实现和应用 (SoftReference,WeakReference,PhantomReference);
Java 引用的强弱关系: StrongReference> SoftReference> WeakReference> PhantomReference
一, StrongReference
强引用: 我们通常使用的引用, 形如 Object o = new Object();
此时从 stack 中的 o, 到 heap 中的 Object 就是强引用; 其他引用强弱的判定规则, 可以查看我上一篇博客 Reference 框架概览 ;
二, SoftReference
软引用: 可以用来表示一些有用但非必须的对象; JVM 会根据使用率和剩余堆空间大小来公共决定什么时候回收 SoftReference;JVM 保证在抛出 OOM 之前会再次扫描回收这些软引用, 如果回收后内存仍不足才会抛出 OOM; 所以在源码的注释中也写了 SoftReference 适合实现内存敏感的缓存;
- public class SoftReference<T> extends Reference<T> {
- /**
- * Timestamp clock, updated by the garbage collector
- */
- 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.
- */
- 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;
- }
- }
看上面的代码, SoftReference 与 Reference 相比多了两个时间戳 clock,timestamp, 并且会在每次 get 的时候更新时间戳;
clock: 这个时间戳是 static 修饰的, 是所有 SoftReference 共有, 由 JVM 维护;
timestamp: 主要用于记录当前对象的存活时间;
回收策略
上面提到 SoftReference 的回收是由使用率和剩余堆空间大小来公共决定的, 那么它是怎么实现的呢?
- openjdk/hotspot/src/share/vm/memory/referencePolicy.cpp
- // Capture state (of-the-VM) information needed to evaluate the policy
- void LRUCurrentHeapPolicy::setup() {
- _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
- assert(_max_interval>= 0,"Sanity check");
- }
- // The oop passed in is the SoftReference object, and not
- // the object the SoftReference points to.
- bool LRUCurrentHeapPolicy::should_clear_reference(oop p, jlong timestamp_clock) {
- jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
- assert(interval>= 0, "Sanity check");
- // The interval will be zero if the ref was accessed since the last scavenge/gc.
- if(interval <= _max_interval) {
- return false;
- }
- return true;
- }
根据上面的代码可以大致知道:
首先计算出了最大堆内存和上次 GC 时剩余的内存;
再用 (剩余内存 / 最大内存 )* SoftRefLRUPolicyMSPerMB 得出到下次 GC 期间软引用的最大 idle 时间;
最后用 clock 和 timestamp 两个时间戳差值得到 SoftReference 的 idle 时间 (每次 get 的时候
this.timestamp = clock;
, 所以 get 之后 idle 时间归零), 如果大于最大 idle 时间则清除;
我们可以简单测试一下, 启动参数:-XX:SoftRefLRUPolicyMSPerMB=2 -Xmx10M -XX:+PrintCommandLineFlags -verbose:gc;
-XX:SoftRefLRUPolicyMSPerMB=2
: 可以参照上面的计算过程调节 SoftReference 的回收频率;
-Xmx10M: 为最大堆内存, 同样可以自行调节,-verbose:gc: 打开 GC 日志,
-XX:+PrintCommandLineFlags
: 打印 JVM 启动参数;
- private static void test03() throws InterruptedException {
- ReferenceQueue queue = new ReferenceQueue();
- Object o = new Object() {
- @Override
- public String toString() {
- return "zhangsan";
- }
- };
- Reference softRef = new SoftReference(o, queue);
- new Monitor(queue).start();
- o = null;
- System.gc();
- log.info("o=null, referent:{}", softRef.get());
- byte[] bytes = new byte[3 * 1024 * 1024];
- System.gc();
- log.info("After GC, referent:{}", softRef.get());
- Thread.sleep(2000);
- System.gc();
- log.info("After GC, referent:{}", softRef.get());
- }
- private static class Monitor extends Thread {
- ReferenceQueue queue;
- public Monitor(ReferenceQueue queue) {
- this.queue = queue;
- }
- @Override
- public void run() {
- while (true) {
- try {
- log.info("remove reference:{}", queue.remove().toString());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- // 打印:
- [main] o=null, referent:zhangsan
- [main] After GC, referent:zhangsan
- [main] After GC, referent:null
- [Thread-0] remove reference:java.lang.ref.SoftReference@bcffe9a
根据不同的参数设置会出现不同的情况, 大家可以自行调节参数, 验证上面的计算规则; 另外如果 - XX:SoftRefLRUPolicyMSPerMB=0, 那么 SoftReference 就应该和 WeakReference 差不多了, 至于是否完全一致, 就留到以后查看 JVM 的时候再确定了;
三, WeakReference
弱引用: 被弱引用关联的对象只能生存到下一次 GC, 当 GC 的时候无论内存是否足够, 使用是否频繁都会被清除; 同样源码注释里面也写了 WeakReference 适合实现 canonicalizing mappings, 比如 WeakHashMap;
- public class WeakReference<T> extends Reference<T> {
- public WeakReference(T referent) {
- super(referent);
- }
- public WeakReference(T referent, ReferenceQueue<? super T> q) {
- super(referent, q);
- }
- }
简单测试, 启动参数:-Xmx300M -XX:+PrintCommandLineFlags -verbose:gc;
- private static void test04() {
- ReferenceQueue queue = new ReferenceQueue();
- Object o = new Object() {
- @Override
- public String toString() {
- return "zhangsan";
- }
- };
- Reference ref = new WeakReference(o, queue);
- new Monitor(queue).start();
- o = null;
- log.info("Before GC, referent:{}", ref.get());
- System.gc();
- log.info("After GC, referent:{}", ref.get());
- }
- // 打印:
- [main] Before GC, referent:zhangsan
- [main] After GC, referent:null
- [Thread-0] remove reference:java.lang.ref.WeakReference@67ac4ff0
可以看到在内存足够的时候, referent 被清除, WeakReference 在下次 GC 的时候随机被清除, 并且 ReferenceQueue 也收到了事件通知;
四, PhantomReference
虚引用: 最弱的一种引用关系, 虚引用对一个对象的生命周期完全没有影响, 设置虚引用的唯一目的就是得到 referent 被回收的事件通知;
- public class PhantomReference<T> extends Reference<T> {
- public T get() {
- return null;
- }
- public PhantomReference(T referent, ReferenceQueue<? super T> q) {
- super(referent, q);
- }
- }
从源码也能看到 get 的时候, 永远返回 null;
同样简单测试一下,
- private static void test06() {
- ReferenceQueue queue = new ReferenceQueue();
- Object o = new Object() {
- @Override
- public String toString() {
- return "zhangsan";
- }
- };
- Reference ref = new PhantomReference(o, queue);
- new Monitor(queue).start();
- o = null;
- log.info("Before GC, referent:{}", ref.get());
- System.gc();
- log.info("After GC, referent:{}", ref.get());
- }
- // 打印:
- [main] Before GC, referent:null
- [main] After GC, referent:null
- [Thread-0] remove reference:java.lang.ref.PhantomReference@661a5fff
可以看到 PhantomReference.get() 始终为 null, 并且当 referent 被回收的时候, 并且 ReferenceQueue 也收到了事件通知;
此外 PhantomReference 和其他引用还有一个很大的不同, 在 ReferenceQueue 中 JVM 并不会帮我们把 referent 字段置为空;
- private static void test07() {
- ReferenceQueue queue = new ReferenceQueue();
- Object o = new Object() {
- @Override
- public String toString() {
- return "zhangsan";
- }
- };
- Reference ref = new PhantomReference(o, queue);
- new Monitor2(queue).start();
- o = null;
- log.info("Before GC, referent:{}", ref.get());
- System.gc();
- log.info("After GC, referent:{}", ref.get());
- }
- private static class Monitor2 extends Thread {
- ReferenceQueue queue;
- public Monitor2(ReferenceQueue queue) {
- this.queue = queue;
- }
- @Override
- public void run() {
- try {
- while (true) {
- Reference ref = queue.poll();
- log.info("remove reference:{}", ref);
- if (ref != null) {
- Field field = Reference.class.getDeclaredField("referent");
- field.setAccessible(true);
- log.info("ReferenceQueue get Referent:{}", field.get(ref));
- ref.clear();
- break;
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- // 打印:
- [main] Before GC, referent:null
- [main] After GC, referent:null
- [Thread-0] remove reference:null
- [Thread-0] remove reference:java.lang.ref.PhantomReference@7b4cba2
- [Thread-0] ReferenceQueue get Referent:zhangsan
这里可以看到从 ReferenceQueue 中取出来的 Reference 仍然可以取到引用对象, 即 referent; 但是在其他引用中打印为 null, 这里可以将上面例子中的 Monitor 改为 Monitor2 测试;
Cleaner:
在 Reference.tryHandlePending() 里面提到的, 主要用于替代 Object.finalize();
- public class Cleaner extends PhantomReference<Object> {
- private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
- static private Cleaner first = null;
- private Cleaner
- next = null,
- prev = null;
- private final Runnable thunk;
- private Cleaner(Object referent, Runnable thunk) {
- super(referent, dummyQueue);
- this.thunk = thunk;
- }
- public static Cleaner create(Object ob, Runnable thunk) {
- if (thunk == null)
- return null;
- return add(new Cleaner(ob, thunk));
- }
- private static synchronized Cleaner add(Cleaner cl) {
- if (first != null) {
- cl.next = first;
- first.prev = cl;
- }
- first = cl;
- return cl;
- }
- private static synchronized boolean remove(Cleaner cl) { }
- 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 只能通过工厂方法创建, 并且所有的 Cleaner 都共同属于同一个 Reference 链表;
代码中的 next,prev 不同于 Reference 中的 next, 他们组成了一个双向链表;
Cleaner 中没有入队操作, 在创建之初就已经加入链表了, 具体代码可以查看
- Reference.tryHandlePending()
- ;
Cleaner 的主要逻辑就是传入一个 clean 线程, 在 referent 引用对象清除的时候, 这行这个清楚操作;
总结
对于上面讲的软引用, 弱引用, 虚引用, 都有一套共同的事件通知机制, 具体逻辑在 Reference 类中; 主要的差别在于引用回收条件的判断, 这部分代码在 JVM 里面;
另外对于 Reference 类还有 FinalReference 没有写, 主要用于当类重写 finalize 方法时, JVM 会将他包装在 FinalReference 里面, 里面的细节比较多, 并且一般不建议使用, 所以暂时没写;
此外《Effective Java》第三版的第八条也讲了避免使用 finalizer 和 cleaner; 详情可以自行查阅;
参考
- http://www.importnew.com/21628.html
- https://www.jianshu.com/p/95a4931ebf01
- https://juejin.im/post/5bbfee46e51d450e5e0cba2f
来源: https://www.cnblogs.com/sanzao/p/10343166.html