垃圾收集器與內(nèi)存分配策略(一)

垃圾收集器與內(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來控制。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容