likes
comments
collection
share

优雅处理对象生命周期:Java四种引用类型指南

作者站长头像
站长
· 阅读数 1

前言

我们使用 Java 时大部分情况都是使用强引用类型对象的,甚至我们很少去考虑是否该用强引用类型,因为大部分场景中强引用类型时最佳选择。但是存在即有气道理,Java 即然提供了这个其他类型的引用那么必然存在不适合强引用类型存在的场景。 接下来我们将探讨下 Java 中四大引用类型

Strong References

List<String> list = new ArrayList<>;
  • 上面申明变量的方式是 Java 代码中很普通的创建方式。这种就是强引用类型。
  • 在这种情况下 GC 回收器是无法回收该对象的,因为目前这块内存分配给 list 对象了。如果我们想回收那么就需要解绑 list 和内存之间的绑定关系
list = null;
  • 通过使用强引用这样能保证我们对象的存活,我们也不需要操心对象内存的分配了,这个对于搞过 C++的同学应该深有感受。
  • 但是有这么一些场景虽然对象仍然存在可达性,但是我们仍然想让 GC 回收他们,比如我有一个 Map 用来起到目录的作用,key 就是我们每个标题类,如果对应的标题类不存在了那么这个 Map 持有的对应的 Key 也应该自动消失,这个功能强引用是无法完成的。这个就需要使用到非强引用中的 WeakReferences 了。

Soft References

  • 弱引用的定义是当 GC 即将面临内存泄漏的时候,GC Collection 自主决定回收该类型对象占用的内存。所有的软引用理论上也只能被软引用所关联使用。
SoftReference<List<String>> listReference = new SoftReference<List<String>>(new ArrayList<String>());
  • 我们创建软引用的方式也很简单粗暴,直接通过 SoftReference 包裹即可。如果你想使用软引用对象我们只需要调用 get 即可
List<String> list = listReference.get();
if (list == null) {
    // object was already cleared
}

除了强引用以外其他三种类型其实各有各的场景, 接下来我们就WeakReferences PhantomFerences 来展开介绍

Weak References

  • 弱引用 表示一旦发生 GC(不管内存是否充足) 对象就会被回收。可以理解成奄奄一息的病人,只要发生一次资源回收它就立马失去存在的意义。
	private static void testWeakReference() {
		for (int i = 0; i < 10; i++) {
			byte[] buff = new byte[1024 * 1024];
			WeakReference<byte[]> sr = new WeakReference<>(buff);
			list.add(sr);
		}
		
		System.gc(); //主动通知垃圾回收
		
		for(int i=0; i < list.size(); i++){
			Object obj = ((WeakReference) list.get(i)).get();
			System.out.println(obj);
		}
	}

  • 针对弱引用的使用场景还有一种就是 WeakHashMap 。该 Map 使用起来了常规 Map 一样,但是里面的 Key 是 WeackReference 类型的,这样做的好处是当对应的 Key 消失时 WeakHashMap 自动就删除了对应的 key 的记录。这种 Map 更像是一种规范性 Map 映射。
  • 类似于目录一样,目录时标题+页数,这里的标题就是一个弱引用对象,如果我们删除了响应的标题对象,在目录中自然就不存在那条目录记录了。

Phantom References

  • 虚引用 或者叫做 幽灵引用,在四大引用中地位存在应该是最弱的一个角色, 他和 Weak References 的唯一区别是需要手动从 quene 中删除。

  • 他和 Java 自带的 finalize 功能不同之处在于,在 quene 队列并不会影响到 GC 的线程,换句话说 Phantom References 只是起到了一个通知的作用。因为 虚引用 是无法通过引用访问对象的,虚引用get 方法就是一个摆设永远是 NULL 。既然获取不到那么他就无法于任何对象进行引用绑定,所以当 GC 之前将 虚引用 发送到队列中,只能是起到一个通知的作用。

优雅处理对象生命周期:Java四种引用类型指南

  • 所以针对 Phantom References 我们必须有一个定时器不断的从 quene 队列中取出虚引用对象手动删除。

Finalize In Java

  • 虚引用 的作用就是在 GC 之前将对象发到一个队列中。这个和 Java 中的 finalize 方法在触发事件点上差不多。finalize in Java 是在 GC 之前判断是否调用的方法。

优雅处理对象生命周期:Java四种引用类型指南

  • 我们通过上述的流程图示能够发现 finalize 相当于首次检测到 GC 时,如果复写了finalize 就会放到低等级的队列中等待执行 finalize 方法。因为时对象本身所以在 finalize 方法中就有可能将对象变成可达状态,比如在 finalizea.setXXX(this) , 这就形成 a 可达 this 对象。

  • 这样就导致第二次 GC 的时候该对象就逃脱掉了。

    • unfinalized: 新建对象会先进入此状态,GC 并未准备执行其 finalize 方法,因为该对象是可达的
    • finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行
    • finalized: 表示GC已经对该对象执行过finalize方法
    • reachable: 表示GC Roots引用可达
    • finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
    • unreachable:对象不可通过上面两种途径可达
public class FinalizationDemo {  
  public static void main(String[] args) {  
    Cake c1 = new Cake(1);  
    Cake c2 = new Cake(2);  
    Cake c3 = new Cake(3);  
      
    c2 = c3 = null;  
    System.gc(); //调用Java垃圾收集器
  }  
}  
 
class Cake extends Object {  
  private int id;  
  public Cake(int id) {  
    this.id = id;  
    System.out.println("Cake Object " + id + "is created");  
  }  
    
  protected void finalize() throws java.lang.Throwable {  
    super.finalize();  //finalize的调用方法
    System.out.println("Cake Object " + id + "is disposed");  
  }  
}

Phantom Reference In Quene

  • PhantomReference 就是虚引用或者称为灵魂引用。为什么这么说因为它像灵魂一样存在但是又无法触摸的到。PhantomReference 的使用场景只能是通知。
@Test  
public void test5() throws InterruptedException {  
    Parent parent = new Parent();  
    Reference reference = new Reference();  
    ReferenceQueue referenceQueue = new ReferenceQueue();  
    Thread thread = new Thread(() -> {  
        while (true){  
            Object obj;  
            if((obj = referenceQueue.poll())!= null){  
                PhantomReference phantomReference = (PhantomReference) obj;  
                parent.setReference(phantomReference.get());  
                System.out.println("queue!!! "+obj);  
            }        }    });  
    thread.start();  
    PhantomReference reference1 = new PhantomReference(reference, referenceQueue);  
    reference = null;  
    System.gc();  
    // 遍历队列的线程是后台常驻进程,执行等级特别低,所以我们这里等待一下  
    Thread.sleep(1000);  
    System.gc();  
    System.out.println(parent.getReference());  
    thread.join();  
}
  • PhantomReference 的其他场景就是监控,我们可以通过监控队列知道哪些对象被回收。

总结

放松一刻

To conquer fear is the beginning of wisdom. — Bertrand Russell