引用
引用
定義
-
傳統(tǒng)定義
- 如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址怜奖,就稱該reference數(shù)據(jù)是代表某塊內(nèi)存我碟、某個對象的引用霹陡。
- 狹隘性:
- 這種定義并沒有什么不對,只是現(xiàn)在看來有些過于狹隘了双藕,一個對象在這種定義下只有<font color=red>“被引用”</font>或者<font color=red>“未被引用”</font>兩種狀態(tài)淑趾,對于描述一些“食之無味,棄之可惜”的對象就顯得無能為力忧陪。
- 比如:我們希望能描述一類對象:當(dāng)內(nèi)存空間還足夠時扣泊,能保留在內(nèi)存之中,如果內(nèi)存空間在進(jìn)行垃圾收集后仍然非常緊張嘶摊,那就可以拋棄這些對象——很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景延蟹。
- 這種定義并沒有什么不對,只是現(xiàn)在看來有些過于狹隘了双藕,一個對象在這種定義下只有<font color=red>“被引用”</font>或者<font color=red>“未被引用”</font>兩種狀態(tài)淑趾,對于描述一些“食之無味,棄之可惜”的對象就顯得無能為力忧陪。
-
在JDK 1.2版之后,Java對引用的概念進(jìn)行了擴(kuò)充叶堆,將引用分為
- 強(qiáng)引用(Strongly Re-ference)阱飘、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種虱颗,這4種引用強(qiáng)度依次逐漸減弱
四種引用介紹
強(qiáng)引用:只要引用關(guān)系存在沥匈,永不回收
- 介紹
- 當(dāng)在Java語言中使用new操作符創(chuàng)建一個新的對象, 并將其賦值給一個變量的時候忘渔,這個變量就成為指向該對象的一個強(qiáng)引用高帖。
- 特點
- <font color=red>無論任何情況下,只要強(qiáng)引用關(guān)系還存在畦粮,垃圾收集器就永遠(yuǎn)不會回收掉被引用的對象,所以強(qiáng)引用容易造成內(nèi)存泄漏</font>散址。
- 回收條件
- 對于一一個普通的對象,如果沒有其他的引用關(guān)系锈玉,只要<font color=red>超過了引用的作用域</font>或者<font color=red>顯式地將相應(yīng)(強(qiáng))引用賦值為null</font>爪飘,就是可以當(dāng)做垃圾被收集了,當(dāng)然具體回收時機(jī)還是要看垃圾收集策略拉背。
軟引用:內(nèi)存不足即回收
- 介紹
- 軟引用是用來描述一些還有用师崎,但非必須的對象。
- 回收時機(jī)
- 被軟引用關(guān)聯(lián)著的對象椅棺,在<font color=red>系統(tǒng)將要發(fā)生內(nèi)存溢出異常前</font>犁罩,會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存两疚,才會拋出內(nèi)存溢出異常床估。
- 用途
- 緩存
- 如果還有空閑內(nèi)存,就可以暫時保留緩存诱渤,當(dāng)內(nèi)存不足時清理掉丐巫,這樣就保證了使用緩存的同時,不會耗盡內(nèi)存。
- 緩存
- 用途
- 當(dāng)內(nèi)存足夠時: 不會回收軟引用的可達(dá)對象递胧。
- 當(dāng)內(nèi)存不夠時: 會回收軟引用的可達(dá)對象碑韵。
使用范式
Object obj = new object();/聲明強(qiáng)引用
SoftReference<0bject> sf = new SoftReference<0bject>(obj);
obj = null;//銷毀強(qiáng)引用
例子
/**
* 軟引用的測試:內(nèi)存不足即回收
* -Xms10m -Xmx10m -XX:+PrintGCDetails
*/
public class SoftReferenceTest {
public static class User {
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int id;
public String name;
@Override
public String toString() {
return "[id=" + id + ", name=" + name + "] ";
}
}
public static void main(String[] args) {
//創(chuàng)建對象,建立軟引用
// SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "songhk"));
//上面的一行代碼缎脾,等價于如下的三行代碼
User u1 = new User(1,"songhk");
SoftReference<User> userSoftRef = new SoftReference<User>(u1);
u1 = null;//取消強(qiáng)引用
//從軟引用中重新獲得強(qiáng)引用對象
System.out.println(userSoftRef.get());
System.gc();
System.out.println("After GC:");
// //垃圾回收之后獲得軟引用中的對象
System.out.println(userSoftRef.get());//由于堆空間內(nèi)存足夠祝闻,所有不會回收軟引用的可達(dá)對象。
//
try {
//讓系統(tǒng)認(rèn)為內(nèi)存資源緊張遗菠、不夠
// byte[] b = new byte[1024 * 1024 * 7];
byte[] b = new byte[1024 * 7168 - 399 * 1024];//恰好能放下數(shù)組又放不下u1的內(nèi)存分配大小 不會報OOM
} catch (Throwable e) {
e.printStackTrace();
} finally {
//再次從軟引用中獲取數(shù)據(jù)
System.out.println(userSoftRef.get());//在報OOM之前联喘,垃圾回收器會回收軟引用的可達(dá)對象。
}
}
}
弱引用:發(fā)現(xiàn)即回收
- 介紹
- 弱引用也是用來描述那些非必須對象辙纬,但是它的強(qiáng)度比軟引用更弱一些豁遭,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止。
- 回收時機(jī)
- 當(dāng)垃圾收集器開始工作牲平,無論當(dāng)前內(nèi)存是否足夠堤框,都會回收掉只被弱引用關(guān)聯(lián)的對象域滥。
- 但是纵柿,由于<font color=red>垃圾回收器的線程通常優(yōu)先級很低</font>,因此启绰,并不一 定能很快地發(fā)現(xiàn)持有弱引用的對象昂儒。在這種情況下,弱引用對象可以存在較長的時間委可。
- 用途
- 軟引用渊跋、弱引用都非常適合來保存那些可有可無的緩存數(shù)據(jù)。
- 如果這么做着倾,當(dāng)系統(tǒng)內(nèi)存不足時拾酝,這些緩存數(shù)據(jù)會被回收,不會導(dǎo)致內(nèi)存溢出卡者。而當(dāng)內(nèi)存資源充足時蒿囤,這些緩存數(shù)據(jù)又可以存在相當(dāng)長的時間,從而起到加速系統(tǒng)的作用崇决。
- 弱引用和軟引用的區(qū)別
- 當(dāng)GC在進(jìn)行回收時材诽,需要通過算法檢查是否回收軟引用對象,而對于弱引用對象恒傻,GC總是進(jìn)行回收脸侥。
- 弱引用對象更容易、更快被GC回收盈厘。
適用范式
Object obj = new object()睁枕; //聲明強(qiáng)引用
WeakReference<0bject> sf = new WeakReference<0bject>(obj);
obj = null; //銷毀強(qiáng)引用
例子
public class WeakReferenceTest {
public static class User {
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int id;
public String name;
@Override
public String toString() {
return "[id=" + id + ", name=" + name + "] ";
}
}
public static void main(String[] args) {
//構(gòu)造了弱引用
WeakReference<User> userWeakRef = new WeakReference<User>(new User(1, "songhk"));
//從弱引用中重新獲取對象
System.out.println(userWeakRef.get());
System.gc();
// 不管當(dāng)前內(nèi)存空間足夠與否外遇,都會回收它的內(nèi)存
System.out.println("After GC:");
//重新嘗試從弱引用中獲取對象
System.out.println(userWeakRef.get());
}
}
虛引用:對象回收跟蹤
- 介紹
- 虛引用也稱為“幽靈引用”或者“幻影引用”拒逮,它是最弱的一種引用關(guān)系。
- 一個對象是否有虛引用的存在臀规,完全不會對其生存時間構(gòu)成影響滩援,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個對象被收集器回收時收到一個系統(tǒng)通知塔嬉。
- 特點
- 它不能單獨使用玩徊,也無法通過虛引用來獲取被引用的對象。當(dāng)試圖通過虛引用的get()方法取得對象時谨究,總是null恩袱。
- 用途
- 為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的在于跟蹤垃圾回收過程。比如:能在這個對象被收集器回收時收到一個系統(tǒng)通知胶哲。
- 由于虛引用可以跟蹤對象的回收時間畔塔,因此,也可以將一些資源釋放操作放置在虛引用中執(zhí)行和記錄鸯屿。
- 使用注意
- 虛引用必須和引用隊列一起使用澈吨。
- 虛引用在創(chuàng)建時必須提供一個引用隊列作為參數(shù)。
- 當(dāng)垃圾回收器準(zhǔn)備回收一個對象時寄摆,如果發(fā)現(xiàn)它還有虛引用谅辣,就會在回收對象后,將這個虛引用加入引用隊列婶恼,以通知應(yīng)用程序?qū)ο蟮幕厥涨闆r桑阶。
- 虛引用必須和引用隊列一起使用澈吨。
使用范式
#在JDK 1. 2版之后提供了PhantomReference類來實現(xiàn)虛引用
object obj = new object();
ReferenceQueuephantomQueue = new ReferenceQueue();
PhantomReference<object> pf = new PhantomReference<object>(obj, phantomQueue);
obj = null;
例子
public class PhantomReferenceTest {
public static PhantomReferenceTest obj;//當(dāng)前類對象的聲明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用隊列
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
if (phantomQueue != null) {
PhantomReference<PhantomReferenceTest> objt = null;
try {
objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (objt != null) {
System.out.println("追蹤垃圾回收過程:PhantomReferenceTest實例被GC了");
}
}
}
}
}
@Override
protected void finalize() throws Throwable { //finalize()方法只能被調(diào)用一次!
super.finalize();
System.out.println("調(diào)用當(dāng)前類的finalize()方法");
obj = this;
}
public static void main(String[] args) {
Thread t = new CheckRefQueue();
t.setDaemon(true);//設(shè)置為守護(hù)線程:當(dāng)程序中沒有非守護(hù)線程時勾邦,守護(hù)線程也就執(zhí)行結(jié)束蚣录。
t.start();
phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
obj = new PhantomReferenceTest();
//構(gòu)造了 PhantomReferenceTest 對象的虛引用,并指定了引用隊列
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);
try {
//不可獲取虛引用中的對象
System.out.println(phantomRef.get());
//將強(qiáng)引用去除
obj = null;
//第一次進(jìn)行GC,由于對象可復(fù)活眷篇,GC無法回收該對象
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
System.out.println("第 2 次 gc");
obj = null;
System.gc(); //一旦將obj對象回收萎河,就會將此虛引用存放到引用隊列中。
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出
null
調(diào)用當(dāng)前類的finalize()方法
obj 可用
第 2 次 gc
追蹤垃圾回收過程:PhantomReferenceTest實例被GC了
obj 是 null
判定對象可以回收的依據(jù)
對象的finalization機(jī)制
- 介紹
- Java語言提供了對象終止(finalization)機(jī)制來允許開發(fā)人員提供對象被銷毀之前的自定義處理邏輯铅歼。
- 特點
- 垃圾回收此對象之前公壤,總會先調(diào)用這個對象的finalize()方法。
- finalize()方法允許在子類中被重寫椎椰,用于<font color=red>在對象被回收時進(jìn)行資源釋放</font>厦幅。通常在這個方法中進(jìn)行一些資源釋放和清理的工作,比如關(guān)閉文件慨飘、套接字和數(shù)據(jù)庫連接等确憨。
- 從功能上來說译荞,finalize()方法與C++ 中的析構(gòu)函數(shù)比較相似,但是Java采用的是基于垃圾回收器的自動內(nèi)存管理機(jī)制休弃,所以finalize()方法在本質(zhì)吞歼,上不同于C++ 中的析構(gòu)函數(shù)。
- 注意事項
- java的使用者永遠(yuǎn)不要主動調(diào)用某個對象的finalize()方法塔猾,應(yīng)該交給垃圾回收機(jī)制調(diào)用篙骡。
- 理由包括下面三點:
- 在finalize() 時可能會導(dǎo)致對象復(fù)活。
- finalize()方法的執(zhí)行時間是沒有保障的丈甸,它完全由Gc線程決定糯俗,極端情況下,若不發(fā)生GC睦擂,則finalize() 方法將沒有執(zhí)行機(jī)會得湘。
- 一個糟糕的finalize ()會嚴(yán)重影響GC的性能。
對象在虛擬機(jī)中的三種狀態(tài)
- 不是所有不可達(dá)對象都必須被回收
- 如果從所有的根節(jié)點都無法訪問到某個對象顿仇,說明對象己經(jīng)不再使用了淘正。一般來說,此對象需要被回收臼闻。但事實上鸿吆,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段些阅。
- 由于finalize ()方法的存在伞剑,虛擬機(jī)中的對象一般處于三種可能的狀態(tài)
- 一個無法觸及的對象有可能在某一個條件下“復(fù)活”自己斑唬,如果這樣市埋,那么對它的回收就是不合理的,為此恕刘,定義虛擬機(jī)中的對象可能的三種狀態(tài)缤谎。如下:
- 可觸及的:從根節(jié)點開始,可以到達(dá)這個對象褐着。
- 可復(fù)活的:對象的所有引用都被釋放坷澡,但是對象有可能在finalize()中復(fù)活。
- 不可觸及的:對象的finalize()被調(diào)用過含蓉,并且沒有復(fù)活频敛,那么就會進(jìn)入不可觸及狀態(tài)。<font color=red>不可觸及的對象不可能被復(fù)活</font>馅扣,因為<font color=red>finalize() 只會被調(diào)用一一次</font>斟赚。
- 一個無法觸及的對象有可能在某一個條件下“復(fù)活”自己斑唬,如果這樣市埋,那么對它的回收就是不合理的,為此恕刘,定義虛擬機(jī)中的對象可能的三種狀態(tài)缤谎。如下:
判定是否可以回收具體過程
判定一個對象objA是否可回收,至少要經(jīng)歷兩次標(biāo)記過程:
- 如果對象objA到GC Roots沒有引用鏈差油,則進(jìn)行第一 次標(biāo)記拗军。
- 進(jìn)行篩選任洞,判斷此對象是否有必要執(zhí)行finalize()方法
- 如果對 象objA沒有重寫finalize()方法,或者finalize ()方法已經(jīng)被虛擬機(jī)調(diào)用過交掏,則虛擬機(jī)視為“沒有必要執(zhí)行”刃鳄,objA被判定為不可觸及的。
- 如果對象objA重寫了finalize()方法叔锐,且還未執(zhí)行過熊尉,那么objA會被插入到F一Queue隊列中,由一個虛擬機(jī)自動創(chuàng)建的狰住、低優(yōu)先級的Finalizer線程觸發(fā)其finalize()方法執(zhí)行齿梁。
- <font color=red>finalize()方法是對象逃脫死亡的最后機(jī)會</font>催植,稍后Gc會對F一Queue隊列中的對象進(jìn)行第二次標(biāo)記。
- 如果objA在finalize()方法中與引用鏈上的任何一個對象建立了聯(lián)系勺择,那么在第二次標(biāo)記時创南,objA會被移出“即將回收”集合省核。
- 之后,對象會再次出現(xiàn)沒有引用存在的情況邻储,在這個情況下旧噪,finalize方法不會被再次調(diào)用,對象會直接變成不可觸及的狀態(tài)宦赠,也就是說米母,<font color=red>一個對象的finalize方法只會被調(diào)用一次</font>。
對象自我拯救的例子
/**
* 測試Object類中finalize()方法妙色,即對象的finalization機(jī)制精拟。
*
*/
public class CanReliveObj {
public static CanReliveObj obj;//類變量虱歪,屬于 GC Root
//此方法只能被調(diào)用一次
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("調(diào)用當(dāng)前類重寫的finalize()方法");
obj = this;//當(dāng)前待回收的對象在finalize()方法中與引用鏈上的一個對象obj建立了聯(lián)系
}
public static void main(String[] args) {
try {
obj = new CanReliveObj();
// 對象第一次成功拯救自己
obj = null;
System.gc();//調(diào)用垃圾回收器
System.out.println("第1次 gc");
// 因為Finalizer線程優(yōu)先級很低笋鄙,暫停2秒怪瓶,以等待它
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
System.out.println("第2次 gc");
// 下面這段代碼與上面的完全相同,但是這次自救卻失敗了
obj = null;
System.gc();
// 因為Finalizer線程優(yōu)先級很低洗贰,暫停2秒,以等待它
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出
第1次 gc
調(diào)用當(dāng)前類重寫的finalize()方法
obj is still alive
第2次 gc
obj is dead
回收方法區(qū)
- 介紹
- 有些人認(rèn)為方法區(qū)(如HotSpot虛擬機(jī)中的元空間或者永久代)是沒有垃圾收集行為的许布,《Java虛擬機(jī)規(guī)范》中提到過可以不要求虛擬機(jī)在方法區(qū)中實現(xiàn)垃圾收集绎晃,事實上也確實有未實現(xiàn)或未能完整實現(xiàn)方法區(qū)類型卸載的收集器存在(如JDK 11時期的ZGC收集器就不支持類卸載)
- 特點:方法區(qū)垃圾收集的“性價比”通常也是比較低的
- 在Java堆中,尤其是在新生代中袁余,對常規(guī)應(yīng)用進(jìn)行一次垃圾收集通吃圩幔可以回收70%至99%的內(nèi)存空間,相比之下掩完,方法區(qū)回收因為苛刻的判定條件积暖,其區(qū)域垃圾收集的回收成果往往遠(yuǎn)低于此。
- 主要回收的兩部分:
- 廢棄的常量
- 回收廢棄常量與回收J(rèn)ava堆中的對象非常類似。
- 舉個常量池中字面量回收的例子
- 假如一個字符串“java”曾經(jīng)進(jìn)入常量池中遍愿,但是當(dāng)前系統(tǒng)又沒有任何一個字符串對象的值是“java”耘斩,換句話說,已經(jīng)沒有任何字符串對象引用常量池中的“java”常量坞笙,且虛擬機(jī)中也沒有其他地方引用這個字面量。如果在這時發(fā)生內(nèi)存回收籍茧,而且垃圾收集器判斷確有必要的話梯澜,這個“java”常量就將會被系統(tǒng)清理出常量池。
- 不再使用的類型
- 判定一個常量是否“廢棄”還是相對簡單吮龄,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了咆疗。需要同時滿足下面三個條件:
- 該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實例胰默。
- 加載該類的類加載器已經(jīng)被回收漓踢,這個條件除非是經(jīng)過精心設(shè)計的可替換類加載器的場景,如OSGi奴迅、JSP的重加載等挺据,否則通常是很難達(dá)成的。
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用暇检,無法在任何地方通過反射訪問該類的方法婉称。
- Java虛擬機(jī)被允許對滿足上述三個條件的無用類進(jìn)行回收王暗,這里說的僅僅是“被允許”,而并不是和對象一樣俗壹,沒有引用了就必然會回收绷雏。
- 關(guān)于是否要對類型進(jìn)行回收虛擬機(jī)提供參數(shù)控制
- HotSpot虛擬機(jī)提供了-Xnoclassgc參數(shù)進(jìn)行控制
- 還可以使用-verbose:class以及-XX:+TraceClass-Loading怖亭、-XX:+TraceClassUnLoading查看類加載和卸載信息
- 其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虛擬機(jī)中使用坤检,-XX:+TraceClassUnLoading參數(shù)需要FastDebug版的虛擬機(jī)支持。
- 判定一個常量是否“廢棄”還是相對簡單吮龄,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了咆疗。需要同時滿足下面三個條件:
- 廢棄的常量
- 回收方法區(qū)的必要性
- 在大量使用反射峭跳、動態(tài)代理缺前、CGLib等字節(jié)碼框架衅码,動態(tài)生成JSP以及OSGi這類頻繁自定義類加載器的場景中,通常都需要Java虛擬機(jī)具備類型卸載的能力逝段,以保證不會對方法區(qū)造成過大的內(nèi)存壓力奶躯。