一、四大引用級別的概念
- 強引用:就是正常的引用,類似于下面:
-
Object object = new Object();
,object就是一個強引用,gc是不會清理一個強引用引用的對象的,即使面臨內存溢出的情況。
- 軟引用:SoftReference,GC會在內存不足的時候清理引用的對象:
SoftReference reference = new SoftReference(object); object = null;
- 弱引用:GC線程會直接清理弱引用對象,不管內存是否夠用:
WeakReference reference = new WeakReference(object); object = null;
- 虛引用:和弱引用一樣,會直接被GC清理,而且通過虛引用的get方法不會得到對象的引用,形同虛設,這里弱引用是可以的:
PhantomReference refernce = new PhantomReference(object); object = null;
二、四大引用級別之間的區別
強引用和軟引用
- 這個比較簡單,軟引用只有在內存不足的時候才會被清理,而強引用什么時候都不會被清理(程序正常運行的情況下),即使是內存不足,利用這一個特性,可以做一些緩存的工作,下面的應用會講到。
軟引用和弱引用
- 弱引用不會影響GC的清理,也就是說當GC檢測到一個對象存在弱引用也會直接標記為可清理對象,而軟引用只有在內存告罄的時候才會被清理
弱引用和虛引用
- 說這個之前要說一下ReferenceQueue的概念,ReferenceQueue是一個隊列,初始化Reference的時候可以作為構造函數的參數傳進去,這樣在該Reference的referent域(Reference用來保存引用對象的屬性)指向的引用對象發生了可達性的變化時會將該Reference加入關聯的隊列中,這個具體的變化根據Reference的不同而不同。官方APi對ReferenceQueue的介紹:
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
- 簡單翻譯就是:當reference object所引用的對象發生了適當的可達性變化時,GC向該隊列追加該引用對象(reference object)。
- 弱引用和虛引用的區別就在于被加入隊列的條件不同,這里主要側重于考慮對象所屬的類重寫了finalize方法,將對象的狀態歸納為三種:finalizable, finalized、reclaimed,分別代表:未執行finalize函數、已經執行finalize函數,已經回收。如果沒有重寫finalize函數的話下面再考慮。
- 虛引用必須和一個ReferenceQueue聯合使用,當GC準備回收一個對象的時候,如果發現該對象還有一個虛引用,就會將這個虛引用加入到與之關聯的隊列.
- 弱引用:當GC第一次試圖回收該引用指向的對象時會執行該對象的finalize方法,然后將該引用加入隊列中,但是該引用指向的對象是可以在finlize函數中“復活”的,所以即使通過Reference的get方法得到的是null,而且reference被加入到了ReferenceQueue,這個對象仍然是存活的,這種現象是有點違背對象正常生命周期的,下面以代碼示例:
package com;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class Main {
static Weak weak;
static class Weak {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize() is called");
weak = this;//復活對象
System.out.println("after finalize ,object is alive again");
}
}
public static void main(String[] args) throws InterruptedException {
weak = new Weak();
ReferenceQueue queue = new ReferenceQueue<>();
WeakReference<Weak> weakReference = new WeakReference<Weak>(weak, queue);
// PhantomReference<Weak> weakReference = new PhantomReference<>(weak,queue);
if (weakReference.get() == null) {
System.out.println("before gc : reference is not available");
} else {
System.out.println("before gc : reference is available");
}
weak = null;
System.gc();//執行GC線程
Thread.sleep(3000);
if (weakReference.get() == null) {
System.out.println("after gc : reference is not available");
} else {
System.out.println("after gc : reference is available");
}
if (queue.poll() == null) {
System.out.println("after gc : reference is not in queue");
} else {
System.out.println("after gc : reference is in queue");
}
weak=null;
System.gc();//再次執行GC
Thread.sleep(3000);
if (queue.poll() == null) {
System.out.println("gc agaain : reference is not in queue");
} else {
System.out.println("gc agaain : reference is in queue");
}
}
}
- output:
before gc : reference is available
finalize() is called
after finalize ,object is alive again
after gc : reference is not available
after gc : reference is in queue
gc agaain : reference is not in queue
- 可以看到,對象在復活之后,reference不可用,而且reference已經被加入到隊列中。
- 虛引用:虛引用只有在對象處于reclaimed狀態時才會將相關reference加入到隊列,可以根據這個特性檢測對象準確的生命周期,比如可以知道對象精確的銷毀時間。
- 和上面相同的代碼,輸出如下:
before gc : reference is not available
finalize() is called
after finalize ,object is alive again
after gc : reference is not available
after gc : reference is not in queue
gc again : reference is in queue
- 我們可以根據輸出看到明顯的區別:
- 弱引用可以得到對象的引用,而虛引用不可以
- 弱引用在第一次被GC清理的時候會調用finalize方法,然后將reference加入到隊列,即使這時候的對象是存貨狀態,而虛引用在第二次GC執行,對象處于reclaimed狀態時才會將reference加入到隊列。
- 順便提一下軟引用,在這個方面,軟引用和弱引用是一樣的,也就是說即使加入到了隊列中,也不能確定對象是否銷毀。
三、四大引用的應用
軟引用
- 緩存敏感數據
- 適用情景:一個web應用,查詢聯系人數據,如果在瀏覽的過程中點擊了返回上一條查詢結果,正常處理就是再重新查詢一次,然而這樣是很浪費時間的,所以如果能將上一次的查詢結果緩存下來就會極大的提升性能,所以可以將查詢結果保存為軟引用,這樣在內存充足的情況下GC都不會釋放掉這個引用指向的對象,下次需要結果的時候可以先判斷一下這個對象是否被釋放,如果沒有就可以直接利用不用再次查詢。
- 當軟引用所指向的對象被回收的時候,通過get方法返回的是null,如果在構造軟引用的時候傳入了一個ReferenceQueue,就是將這個reference加入到隊列,然后我們可以清理這些無用的reference。實際上我們最好這樣做,否則會造成大量無用reference導致的內存泄漏。
- 下面舉一個緩存的例子:
package com;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
public class Main {
}
class People{
String id;
String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class PeopleCache {
private static PeopleCache singlonCache;
private HashMap<String, PeoReference> refChache;//用來存儲緩存的數據
private ReferenceQueue queue;
//自定義引用類,增加屬性_key,方便在緩存數據中查找
static class PeoReference extends SoftReference {
private String _key;
public PeoReference(People referent, ReferenceQueue q) {
super(referent, q);
_key = referent.getId();
}
}
private PeopleCache(){
this.queue=new ReferenceQueue();
this.refChache=new HashMap<>();
}
public static PeopleCache getInstance(){
if(singlonCache==null){
singlonCache=new PeopleCache();
}
return singlonCache;
}
public void cachePeople(People people){
cleanCache();//清除已經標記為垃圾的引用
PeoReference reference = new PeoReference(people, queue);
refChache.put(people.getId(), reference);//將對象的軟引用保存到緩存中
}
public void cleanCache(){
PeoReference reference = null;
while ((reference = (PeoReference)queue.poll())!=null){
refChache.remove(reference._key);
}
}
public People getCachedPeople(String key){
People people = null;
if (refChache.containsKey(key)){
people= (People) refChache.get(key).get();
System.out.println("get object from cache");
}else{
people = new People();
System.out.println("get object from database or web server");
}
return people;
}
}
- 當需要查詢數據的時候先獲取一個PeopleCache實例,然后使用getCachedPeople方法試圖去獲取指定ID的數據,如果緩存中有的話就從緩存中獲取,沒有的話就正常查詢獲取,并且將查詢結果緩存到HashMap。
弱引用
- WeakHashMap
- 具體使用和HashMap一樣,但是它的鍵存放的是對象的弱引用,如果該弱引用指向的對象被垃圾回收了,WeakHashMap就會刪除對應的數據(鍵值對)。
- 用來緩存那些非必需存在的數據。
虛引用