垃圾收集器與內(nèi)存分配策略之--對象已死嗎
GC關(guān)注的問題其實就是三個:
- 哪些內(nèi)存可以被回收
- 何時回收
- 如何回收
所有的GC問題都是關(guān)于這三點的描述。
對于JVM來說,首先線程獨有的三塊(程序計數(shù)器、棧、本地方法棧)都是隨著線程的開始而創(chuàng)建,線程結(jié)束而消亡,一個棧幀在開始時內(nèi)存已經(jīng)固定,所以GC沒必要處理這部分內(nèi)存。我們所說的GC都是針對堆和方法區(qū)而言的。
對象已死嗎
如何判斷一個對象已死?一般主流兩種方法:引用計數(shù)法和可達(dá)性分析法
引用計數(shù)法
很簡單,每個對象維護(hù)一個引用計數(shù)器,當(dāng)某個引用指向這個對象時,就讓這個引用計數(shù)器加1。當(dāng)一個對象的引用計數(shù)器為0,說明對象已死。
致命的缺點:
不好解決循環(huán)引用問題。
可達(dá)性分析法
通過一組“GC Root”的對象作為起始點,向下搜索,走過的路徑上的每個對象是還活著的。不可達(dá)的對象就是死的。HotSpot用的就是這個方法。
Java中可被作為GC Root的對象有四種:
- 棧中引用指向的對象
- 方法區(qū)中static引用指向的對象
- 方法區(qū)中常量引用指向的對象
- 本地方法棧中引用指向的對象
再談引用
一般我們說的引用都是指強(qiáng)引用。
Object obj = new Object();//obj就是一個強(qiáng)引用
而Java中引入一些“弱”的引用。目的為了引入一種這樣的對象:當(dāng)內(nèi)存足夠時,對象保留內(nèi)存中,當(dāng)內(nèi)存不夠時,這些對象可被回收。
Java里引用分為四種:強(qiáng)引用、軟引用、弱引用、虛引用。引用強(qiáng)度逐漸減弱。
這四種引用的區(qū)別就是指向的對象的生存時間不一樣。
- 強(qiáng)引用
這個就是我們普通說的引用,如果一個對象存在這種引用。即時內(nèi)存要溢出,這些對象也不能被回收。 - 軟引用
在要發(fā)生內(nèi)存溢出之前,會先將這些引用指向的對象回收調(diào)。(其中這種說法不對,應(yīng)該是一個對象只有軟引用指向它時)。
比如我們定義一個軟引用:
SoftReference<String> s = new SoftReference<String>(new String("123"));
- 弱引用
生命周期為下一次GC之前。無論內(nèi)存是否足夠,這些指向的對象都會被回收。
WeakReference<String> w = new WeakReference<String>(new String("234"));
- 虛引用
一個對象是否有虛引用和它的生命周期毫無關(guān)系。虛引用的唯一目的就是在對象被回收的時候會受到一個系統(tǒng)通知。
ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
PhantomReference<String> p = new PhantomReference<>(new String("434"), referenceQueue);//當(dāng)對象被回收時,referenceQueue就會受到一個消息
一般軟引用和弱引用都用于本地緩存中,每次用時判斷一下是否被回收,如果被回收再去到數(shù)據(jù)庫里撈。
SoftReference<String> s = new SoftReference<String>(new String("123"));
if (null == s.get()) {//如果已被回收
//到數(shù)據(jù)庫里撈
}else {//如果沒被回收
System.out.println(s.get());//直接使用
}
生存還是死亡
一個對象GC Root不可達(dá)并不代表著立刻死亡。一個對象的死亡要經(jīng)歷兩次標(biāo)記。
具體過程如下:
- 當(dāng)GC Root不可達(dá)之后被標(biāo)記一次,
- 然后判斷這個對象是否需要執(zhí)行finalize方法。
判斷不需要的標(biāo)準(zhǔn)是:對象沒有重寫finalize方法或finalize已被虛擬機(jī)執(zhí)行過。[也就是說對象finalize只能被執(zhí)行一次] - 如果判斷需要執(zhí)行,則將這個對象丟到F-Queue中,然后虛擬機(jī)會有一個Finalize線程去消費執(zhí)行對象的finalize方法,但并不會等待執(zhí)行結(jié)束。如果在執(zhí)行finalize方法中[在被標(biāo)記第二次之前]又把此對象關(guān)聯(lián)到GC Root上,這個對象就又會活過來。【注意這種現(xiàn)象一個對象只能出現(xiàn)一次,因為finalize方法只可能會執(zhí)行一次】
- 所以說一個對象的finalize被執(zhí)行了,這個對象可能還是活著的。且finalize方法被強(qiáng)烈建議不要使用,這個方法不會有人保證它是否會執(zhí)行、何時執(zhí)行、何時執(zhí)行結(jié)束。
回收方法區(qū)
回收方法區(qū)的性價比很低,且Java虛擬機(jī)規(guī)范中并沒有強(qiáng)制要求要回收方法區(qū)。
回頭方法區(qū)主要回收兩點:常量和無用的類。
- 常量
這里說的就是常量池中常量,比如字面量和符號引用。
判斷是否應(yīng)該回收比較簡單,比如字面量:系統(tǒng)中沒人叫這個東西了,自然就可以被回收了。 - 無用的類
這個判斷起來就比較麻煩了。
主要由三點:
該類的實例已全部被回收
加載該類的ClassLoader已被回收
該類的Class對象無引用且無法使用反射來調(diào)用
滿足這三點是可以被回收,但也不是一定就會被回收。可以使用-Xnoclassgc來控制。