Think in Java(三)垃圾回收

  1. finalize
    java允許在類中定義一個名爲finalize()的方法:一旦垃圾回收器準備好釋放對象佔用的存儲空間,將首先調用其finalize()方法,並且在下次垃圾回收動作發生時,纔會真正回收對象佔用的內存。
    一般情況下,我們不要過多使用finalize,它的存在只是爲了處理java中在使用“本地方法”情況下調用C的malloc()分配存儲空間,然後需要在finalize()中調用本地方法free()去釋放內存。
  2. 垃圾回收機制
    1)基於計數技術
    對個對象都含有一個引用計數器,當有引用連接至對象時,引用計數加1.當引用離開作用域或指向其他對象或被置爲null時,引用計數減1.垃圾回收器會在含有全部對象的列表遍歷,當發現某個對象的引用計數爲0時,就釋放器佔用的空間。
    缺陷:如果對象之間存在循環引用,可能會出現“對象應該被回收,但引用計數不爲零”的情況。所需工作量極大。
    2)“活”對象
    對任何“活”的對象,一定能最終追溯到其存活在堆棧或靜態存儲區之中的引用。這個引用鏈條可能會穿過數個對象層次。由此,如果從堆棧和靜態存儲區開始,遍歷(類似於圖)所有的引用,就能找到所有“活”的對象。這就解決了“交互自引用的對象組”的問題——這種對象根本不會被發現,因此也就被自動回收了。
    3)自適應(基於“活”對象)
    在這種方式下,Java虛擬機將採用一種自適應的垃圾回收技術。至於如何處理找到的存活對象,取決於不同的Java虛擬機實現。
  3. 具體實現
    1)停止-複製
    先暫停程序的運行,然後將所有存活的對象從當前堆複製到另一個堆,沒有被複制的全部都是垃圾。當對象被複制到新堆時,它們是一個挨着一個的,所以新堆保持緊湊排列。同時,所有指向這些對象的引用都必須修正。位於堆或靜態存儲區的引用可以直接被修正,但還有其他指向這些對象的引用,它們要在遍歷的過程中才能被找到(可以想象成有個表格,將舊地址映射至新地址)。
    缺點:效率低,主要兩個原因。首先得有兩個堆,然後在這兩個分離的堆之間來回倒騰,從而得維護比實際需要多一倍的空間。第二個問題在於複製,程序在進入穩定狀態之後,可能只產生少量垃圾,甚至沒有垃圾,儘管如此,複製式回收器仍然會將所有內存自一處複製到另一處,這很浪費。
    2)標記-清掃
    要是沒有新垃圾產生,就會轉換到另一種工作模式(即“自適應”)——標記-清掃。從堆棧和靜態存儲區出發,遍歷所有引用,進而找出所有存活的對象。每當找到一個存活對象,就會給對象設一個標誌,這個過程不會回收任何對象,只有全部標記工作完成的時候,清理動作纔會開始。在清理過程中,沒有標記的對象將被釋放,不會發生任何複製動作。所以剩下的堆空間是不連續的。標記-清掃方式速度相當慢,但是隻產生少量垃圾甚至不產生垃圾時,它的速度就很快了。
    3)分代
    內存以“塊”爲單位。如果對象較大,它會佔用單獨的塊,每個塊都有相應的代數來記錄它是否還存活。垃圾回收器會定期進行完整的清理動作——大型對象不會被複制(只是其代數會增加),內含小型對象的那些塊則是被複制並整理。
    總結:Java虛擬機會進行監視,如果所有對象都很穩定,切換到“標記-清掃”方式;同樣,要是堆空間出現很多碎片,就會切換回“停止-複製”方式。這就是“自適用”技術。
    Java虛擬機中有許多附件技術用以提升速度。例如“即時”(Just-In-Time,JIT)編譯器技術。採用惰性評估,即時編譯器只在必要的時候編譯代碼。這樣從不會被執行的代碼也許就壓根不會被JIT編譯。代碼每次被執行的時候都會做一些優化,所以執行的次數越多,它的速度就越快。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章