對象已死嗎

 

在堆裏面存放着Java世界中幾乎全部的對象實例,垃圾收集器在對堆進行回收前,第一 件事情就是要肯定這些對象之中哪些還「存活」着,哪些已經「死去」(即不可能再被任何途徑 使用的對象)。java

3.2.1 引用計數算法 程序員

不少教科書判斷對象是否存活的算法是這樣的:給對象中添加一個引用計數器,每當有 一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器爲0 的對象就是不可能再被使用的。面試

做者面試過不少的應屆生和一些有多年工做經驗的開發人 員,他們對於這個問題給予的都是這個答案。 客觀地說,引用計數算法(Reference Counting)的實現簡單,斷定效率也很高,在大部 分狀況下它都是一個不錯的算法,也有一些比較著名的應用案例,例如微軟公司的 COM(Component Object Model)技術、使用ActionScript 3的FlashPlayer、Python語言和在遊 戲腳本領域被普遍應用的Squirrel中都使用了引用計數算法進行內存管理。可是,至少主流 的Java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的緣由是它很難解決對象 之間相互循環引用的問題。 舉個簡單的例子,請看代碼清單3-1中的testGC()方法:對象objA和objB都有字段 instance,賦值令objA.instance=objB及objB.instance=objA,除此以外,這兩個對象再無任何引 用,實際上這兩個對象已經不可能再被訪問,可是它們由於互相引用着對方,致使它們的引 用計數都不爲0,因而引用計數算法沒法通知GC收集器回收它們。算法

3.2.2 可達性分析算法 緩存

在主流的商用程序語言(Java、C#,甚至包括前面提到的古老的Lisp)的主流實現中, 都是稱經過可達性分析(Reachability Analysis)來斷定對象是否存活的。這個算法的基本思 路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所 走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連 (用圖論的話來講,就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。如 圖3-1所示,對象object 五、object 六、object 7雖然互相有關聯,可是它們到GC Roots是不可達 的,因此它們將會被斷定爲是可回收的對象。框架

在Java語言中,可做爲GC Roots的對象包括下面幾種: 虛擬機棧(棧幀中的本地變量表)中引用的對象。 方法區中類靜態屬性引用的對象。 方法區中常量引用的對象。 本地方法棧中JNI(即通常說的Native方法)引用的對象。函數

3.2.3 再談引用 ui

不管是經過引用計數算法判斷對象的引用數量,仍是經過可達性分析算法判斷對象的引 用鏈是否可達,斷定對象是否存活都與「引用」有關。this

在JDK 1.2之前,Java中的引用的定義很 傳統:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊 內存表明着一個引用。這種定義很純粹,可是太過狹隘,一個對象在這種定義下只有被引用 或者沒有被引用兩種狀態,對於如何描述一些「食之無味,棄之惋惜」的對象就顯得無能爲 力。咱們但願能描述這樣一類對象:當內存空間還足夠時,則能保留在內存之中;若是內存 空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這 樣的應用場景。 線程

在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類來實現弱引 用。 

虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引 用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一 個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。在 JDK 1.2以後,提供了PhantomReference類來實現虛引用。

3.2.4 生存仍是死亡 

即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處 於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:若是對象在進行可達 性分析後發現沒有與GC Roots相鏈接的引用鏈,那它將會被第一次標記而且進行一次篩選, 篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或 者finalize()方法已經被虛擬機調用過,虛擬機將這兩種狀況都視爲「沒有必要執行」。

 若是這個對象被斷定爲有必要執行finalize()方法,那麼這個對象將會放置在一個叫作 F-Queue的隊列之中,並在稍後由一個由虛擬機自動創建的、低優先級的Finalizer線程去執行 它。這裏所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣作 的緣由是,若是一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情 況),將極可能會致使F-Queue隊列中其餘對象永久處於等待,甚至致使整個內存回收系統 崩潰。finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象 進行第二次小規模的標記,若是對象要在finalize()中成功拯救本身——只要從新與引用鏈 上的任何一個對象創建關聯便可,譬如把本身(this關鍵字)賦值給某個類變量或者對象的 成員變量,那在第二次標記時它將被移除出「即將回收」的集合;若是對象這時候尚未逃 脫,那基本上它就真的被回收了。從代碼清單3-2中咱們能夠看到一個對象的finalize()被 執行,可是它仍然能夠存活。

從代碼清單3-2的運行結果能夠看出,SAVE_HOOK對象的finalize()方法確實被GC收 集器觸發過,而且在被收集前成功逃脫了。

 另一個值得注意的地方是,代碼中有兩段徹底同樣的代碼片斷,執行結果倒是一次逃 脫成功,一次失敗,這是由於任何一個對象的finalize()方法都只會被系統自動調用一次, 若是對象面臨下一次回收,它的finalize()方法不會被再次執行,所以第二段代碼的自救行 動失敗了。

 須要特別說明的是,上面關於對象死亡時finalize()方法的描述可能帶有悲情的藝術色 彩,筆者並不鼓勵你們使用這種方法來拯救對象。相反,筆者建議你們儘可能避免使用它,因 爲它不是C/C++中的析構函數,而是Java剛誕生時爲了使C/C++程序員更容易接受它所作出的 一個妥協。它的運行代價高昂,不肯定性大,沒法保證各個對象的調用順序。有些教材中描 述它適合作「關閉外部資源」之類的工做,這徹底是對這個方法用途的一種自我安慰。 finalize()能作的全部工做,使用try-finally或者其餘方式均可以作得更好、更及時,因此筆 者建議你們徹底能夠忘掉Java語言中有這個方法的存在。

3.2.5 回收方法區

不少人認爲方法區(或者HotSpot虛擬機中的永久代)是沒有垃圾收集的,Java虛擬機規 範中確實說過能夠不要求虛擬機在方法區實現垃圾收集,並且在方法區中進行垃圾收集 的「性價比」通常比較低:在堆中,尤爲是在新生代中,常規應用進行一次垃圾收集通常能夠 回收70%~95%的空間,而永久代的垃圾收集效率遠低於此。 

永久代的垃圾收集主要回收兩部份內容:廢棄常量和無用的類。回收廢棄常量與回收 Java堆中的對象很是相似。以常量池中字面量的回收爲例,假如一個字符串「abc」已經進入了 常量池中,可是當前系統沒有任何一個String對象是叫作「abc」的,換句話說,就是沒有任何 String對象引用常量池中的「abc」常量,也沒有其餘地方引用了這個字面量,若是這時發生內 存回收,並且必要的話,這個「abc」常量就會被系統清理出常量池。常量池中的其餘類(接 口)、方法、字段的符號引用也與此相似。 

斷定一個常量是不是「廢棄常量」比較簡單,而要斷定一個類是不是「無用的類」的條件則 相對苛刻許多。類須要同時知足下面3個條件才能算是「無用的類」:

該類全部的實例都已經被回收,也就是Java堆中不存在該類的任何實例。 

加載該類的ClassLoader已經被回收。 

該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該 類的方法。 

虛擬機能夠對知足上述3個條件的無用類進行回收,這裏說的僅僅是「能夠」,而並非 和對象同樣,不使用了就必然會回收。是否對類進行回收,HotSpot虛擬機提供了-Xnoclassgc 參數進行控制,還可使用-verbose:class以及-XX:+TraceClassLoading、-XX: +TraceClassUnLoading查看類加載和卸載信息,其中-verbose:class和-XX: +TraceClassLoading能夠在Product版的虛擬機中使用,-XX:+TraceClassUnLoading參數須要 FastDebug版的虛擬機支持。 在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁 自定義ClassLoader的場景都須要虛擬機具有類卸載的功能,以保證永久代不會溢出

相關文章
相關標籤/搜索