在GC 判斷對象是否可用時,無論是通過引用計數算法判斷對象的引用數量,還是通過可達性分析算法判斷對象的引用鏈是否可達,都與“引用”有關。在JDK 1.2 以前,Java 中的引用定義很傳統:如果reference 類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表着一個引用。
在此我們先拓展一下引用,即對象的訪問定位
建立對象是爲了使用對象,我們的Java 程序需要通過棧上的reference數據來操作堆上的具體對象。由於Java 虛擬機規範中並沒有規定引用應該通過何種方式去定位、訪問堆中的具體位置,所以對象訪問方式取決於虛擬機實現而定的。目前主流的訪問方式有使用句柄和直接指針兩種
- 如果使用句柄,那麼Java 堆中將會劃分出一塊內存作爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的及具體地址信息,如下:
- 如果使用直接指針訪問,那麼Java 堆對象的佈局就必須考慮如何防止訪問類型數據的相關信息,而reference中存儲的直接就是對象地址,如下:
對比:
- 使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象移動(垃圾收集時移動對象是非常普遍的)時只會改變句柄中的實例數據指針,而reference本身不需要修改
- 使用直接指針訪問的最大好處就是速度更快,節省了一次指針定位的時間開銷,由於對象的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本
拉回四種引用,在我們實際垃圾收集時,總會有些對象是“食之無味,棄之可惜”的雞肋,類似於我們平常收集整理自己的物品,有些衣服可以丟,但不丟沒準也還能穿一次,那我們可能會考慮櫥櫃夠不夠大,還是否有地方 存放這類衣服。所以在我們GC 的時候也希望能達到這種:當內存空間還足夠時,則能保存在內存之中;如果內存空間在進行垃圾收集後還是非常緊張,則可以拋棄這些對象。 很多系統的緩存功能都符合這樣的場景。
在JDK 1.2 之後,Java 對引用的概念進行了擴充,將引用分爲 強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference) 4種。這4種 引用強度依次逐漸減弱
強引用就是指在程序需代碼中普遍存在的,類似“Object obj = new Object()” 這類的引用,只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的對象。
軟引用 是用來描述一些還有用但並非必需的對象,對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。在JDK 1.2 之後,提供了SoftReference 類來實現軟引用。
弱引用 也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用管理連的對象。在JDK 1.2 之後,提供了WeakReference 類來是休閒弱引用
虛引用 也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK1.2之後,提供了PhantomReference類來實現虛引用
我們首先知道 對象在被判定死亡的過程中會調用一次其finalize()方法,我們以此來驗證下是否對象是否被垃圾收集器回收。
package com.xnccs.cn.share.model;
public class Product {
private long price; //價格
private String name; //名稱
private String desc; //備註
private String size; //大小
private byte[] bytes;
public Product(long price, String name, String desc, String size, byte[] bytes) {
super();
this.price = price;
this.name = name;
this.desc = desc;
this.size = size;
this.bytes = bytes;
}
public void finalize() throws Throwable{
super.finalize();
System.out.println("調用了finalize方法, 產品:"+ this.name +" 被回收");
}
@Override
public String toString() {
return "Product [price=" + price + ", name=" + name + ", desc=" + desc
+ ", size=" + size + "]";
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
package com.xnccs.cn.share;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;
import com.xnccs.cn.share.model.Product;
/**
* VM: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* @author j_nan
*
*/
public class ReferenceTest {
private static ReferenceQueue<Product> referenceQueue = new ReferenceQueue<Product>();
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws InterruptedException{
reference(); //強引用
// reference_weak(); //弱引用
// reference_weak_queue();
// weak_hashmap(); //WeakHashMap 使用
}
private static void reference() throws InterruptedException{
//new 一個產品
Product pro = createOne();
//給產品加個編號
HashMap<Product,Integer> map = new HashMap();
//map中放入強引用
map.put(pro, 1);
//這個產品不再需要
pro = null;
//發生GC 進行回收
System.gc();
Thread.sleep(500);
// System.gc();
// Thread.sleep(1000*30);
}
/**
*
* @throws InterruptedException
*/
private static void reference_weak() throws InterruptedException{
//new 一個產品
Product pro = createOne();
//給產品加個編號
HashMap<WeakReference,Integer> map = new HashMap();
//弱引用對象
WeakReference<Product> weakReference = new WeakReference<Product>(pro);
// 根據引用獲取對象
System.out.println("根據弱引用獲取對象:"+weakReference.get());
//map中放入弱引用
map.put(weakReference, 1);
//這個產品不再需要
pro = null;
System.out.println("-----------------------------發生GC------------------------------");
//發生GC 進行回收
System.gc();
// 根據引用獲取對象
System.out.println("輸出產品:"+weakReference.get());
Thread.sleep(500);
// System.gc();
// Thread.sleep(1000*30);
}
/**
*
* @throws InterruptedException
*/
private static void reference_weak_queue() throws InterruptedException{
//new 一個產品
Product pro = createOne();
//給產品加個編號
HashMap<WeakReference,Integer> map = new HashMap();
//引用隊列
WeakReference<Product> weakReference = new WeakReference<Product>(pro,referenceQueue);
// 根據引用獲取對象
System.out.println("輸出產品:"+weakReference.get());
//map中放入弱引用
map.put(weakReference, 1);
//這個產品不再需要
pro = null;
//
System.out.println("GC前 引用隊列:"+referenceQueue.poll());
System.out.println("-----------------------------發生GC------------------------------");
//發生GC 進行回收
System.gc();
Thread.sleep(500);
// 根據引用獲取對象
System.out.println("輸出產品:"+weakReference.get());
System.out.println("GC1 後 引用隊列:"+referenceQueue.poll());
System.gc();
System.out.println("GC2 後 引用隊列:"+referenceQueue.poll());
}
private static void weak_hashmap() throws InterruptedException{
Product pro = new Product(10000,"產品A","測試專用","100kg",new byte[3*_1MB]);
WeakHashMap<WeakReference<Product>,Integer> map = new WeakHashMap<WeakReference<Product>,Integer>();
map.put(new WeakReference<Product>(pro), 1);
pro = null;
System.gc();
Thread.sleep(500);
System.out.println(map.size());
}
private static Product createOne(){
return new Product(10000,"產品A","測試專用","100kg",new byte[8*_1MB]);
}
}
這裏自己寫了一個例子,大家可以自行跑一下
1。 如果是強引用,直接new 的情況下,你會發現即便是將對象Product 置爲null 但在發生GC時對象Product 裏的finalize() 方法並未執行打印,因爲map 還佔着它的一個引用,但如果使用弱引用類WeakReference 來存放,發生GC 就會打印
2。WeakHashMap 如果其中某一個引用失效,則Size 會同步減小,不會出現null 的情況導致錯誤,以及內存泄露的問題
3。類的註釋裏提供了執行的條件,可以看一下堆棧的實際大小來判斷對象的回收情況
4。 ReferenceQueue:Reference這個類裏面在構造函數的時候有兩種選擇,一種是給它傳入一個ReferenceQueue,一種是不傳,如果不傳的話,等這個對象的內存被回收了,直接從Active變爲Inactive狀態,如果我們傳入了ReferenceQueue,那麼當對象的內存回收的時候會經歷一個過程,從Active->Pending->Enqueued->Inactive。pending狀態就是等待着進入ReferenceQueue隊列的這樣一個狀態,說白了它目前還沒被回收,只是對象的引用(用戶代碼中的引用)被移除了,pending保存了這個引用,回收的過程中,ReferenceHandler這個線程會把該對象的引用(pending)放入到我們在構造函數時傳入的那個隊列裏面
在調用:reference_weak() 方法時
輸出:
[ParOldGen: 8673k->481k(10240k)] 含義:GC 前該內存區域已使用容量->GC 後該內存區域已使用容量(該內存區域總容量)。方括號外的 8705K->481K(19456K) 表示: GC 前Java 堆已使用容量->GC後Java 堆已使用容量(Java 堆總容量)。 可以看出確實被回收了。