前言
我们使用 Java 时大部分情况都是使用强引用类型对象的,甚至我们很少去考虑是否该用强引用类型,因为大部分场景中强引用类型时最佳选择。但是存在即有气道理,Java 即然提供了这个其他类型的引用那么必然存在不适合强引用类型存在的场景。 接下来我们将探讨下 Java 中四大引用类型
Strong References
java
List<String> list = new ArrayList<>;
- 上面申明变量的方式是 Java 代码中很普通的创建方式。这种就是强引用类型。
- 在这种情况下
GC
回收器是无法回收该对象的,因为目前这块内存分配给 list 对象了。如果我们想回收那么就需要解绑 list 和内存之间的绑定关系
java
list = null;
- 通过使用强引用这样能保证我们对象的存活,我们也不需要操心对象内存的分配了,这个对于搞过 C++的同学应该深有感受。
- 但是有这么一些场景虽然对象仍然存在可达性,但是我们仍然想让 GC 回收他们,比如我有一个 Map 用来起到目录的作用,key 就是我们每个标题类,如果对应的标题类不存在了那么这个 Map 持有的对应的 Key 也应该自动消失,这个功能强引用是无法完成的。这个就需要使用到非强引用中的
WeakReferences
了。
Soft References
- 弱引用的定义是当
GC
即将面临内存泄漏的时候,GC Collection 自主决定回收该类型对象占用的内存。所有的软引用理论上也只能被软引用所关联使用。
java
SoftReference<List<String>> listReference = new SoftReference<List<String>>(new ArrayList<String>());
- 我们创建软引用的方式也很简单粗暴,直接通过
SoftReference
包裹即可。如果你想使用软引用对象我们只需要调用get
即可
java
List<String> list = listReference.get();
if (list == null) {
// object was already cleared
}
除了强引用以外其他三种类型其实各有各的场景, 接下来我们就
WeakReferences
和PhantomFerences
来展开介绍
Weak References
弱引用
表示一旦发生GC(不管内存是否充足)
对象就会被回收。可以理解成奄奄一息的病人,只要发生一次资源回收它就立马失去存在的意义。
java
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 之前将虚引用
发送到队列中,只能是起到一个通知的作用。
- 所以针对
Phantom References
我们必须有一个定时器不断的从quene
队列中取出虚引用对象手动删除。
Finalize In Java
虚引用
的作用就是在GC
之前将对象发到一个队列中。这个和Java
中的finalize
方法在触发事件点上差不多。finalize in Java
是在GC
之前判断是否调用的方法。
-
我们通过上述的流程图示能够发现
finalize
相当于首次检测到 GC 时,如果复写了finalize
就会放到低等级的队列中等待执行finalize
方法。因为时对象本身所以在finalize
方法中就有可能将对象变成可达状态,比如在finalize
中a.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:对象不可通过上面两种途径可达
java
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
的使用场景只能是通知。
java
@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