Thinking In Java筆記(第五章 初始化與清理(二))

第五章 初始化與清理(二)

5.5 清理:終結處理和垃圾回收

    清理的工作常常被忽略,Java有垃圾回收器負責回收無用對象佔據的內存資源。但也有特殊情況:假定對象(並非使用new)獲得了一塊”特殊”的內存區域,由於垃圾回收器只知道釋放那些由new分配的內存,所以不知道如何釋放特殊內存。Java允許在類中定義一個名爲finalize()的方法,工作原理”假定”是這樣的:一旦垃圾回收器準備好釋放對象佔用的存儲空間,首先調用其finalize()方法,並且在下一次垃圾回收動作發生時,纔會真正回收對象佔用的內存。

    Java的finalize()和C++的析構函數有所不同,C++中對象一定會被銷燬(如果程序中沒有缺陷的話),而Java中對象並非總是被垃圾回收。即:1.對象可能不被垃圾回收。2.垃圾回收並不等於”析構”

    Java並未提供”析構函數”或類似的概念,要做的類似的清理工作,必須自己動手創建一個執行清理工作的普通方法。當”垃圾回收”發生時(不能保證一定會發生),finalize()得到了調用,相應的工作就會進行,如果垃圾回收沒有發生,就不會被調用。

    只要程序沒有瀕臨存儲空間用完的那一刻,對象佔用的控件就總也得不到釋放,如果程序執行結束,並且垃圾回收器一直都沒有釋放你創建的任何對象的存儲空間,隨着程序的退出,資源會全部還給操作系統。這個策略是恰當的,因爲垃圾回收本身也佔用內存控件,如果不使用,內存開銷會變小。

5.5.1 finalize()用途何在

    垃圾回收有關的任何行爲(尤其是finalize()方法),它們也必須同內存及回收有關。finalize()方法存在的意義是爲了回收那些用new創建出來的對象之外的特殊存儲空間,是由於在分配內存時,可能採用了類似C語言中的做法,而非Java中的通常做法(new)。這種情況主要發生在使用”本地方法”的情況下,本地方法是一種在Java中調用非Java代碼的方式。本地方法目前只支持C和C++,但它們可以調用其他語言寫的代碼。

    在非Java代碼中,也許會調用C的malloc()函數系列來分配存儲空間,而且除非調用free()函數,否則存儲空間將得不到釋放,從而造成內存泄漏。free()是C和C++中的函數,所以需要在finalize()中用本地方法調用它。

5.5.2 你必須實施清理

    要清理一個對象,用戶必須在需要清理的時刻調用執行清理動作的方法。在C++中,所有對象都會被銷燬,都應該被銷燬。如果在C++中創建了一個局部對象(也就是在堆棧上創建,這在Java中行不通),此時的銷燬動作發生在以”右花括號”爲邊界的、此對象作用域的末尾處。如果對象是用new創建的,那麼當程序員調用C++的delete操作符(Java沒有這個命令),就會調用相應的析構函數。如果沒有調用delete,那永遠不會調用析構函數,這樣會出現內存泄漏。

    Java中不允許創建局部對象,必須使用new創建對象。在Java中,也沒有釋放對象的delete,垃圾回收器會幫你釋放存儲空間。

5.5.3 終結條件

    通常不能指望finalize(),必須創建其他的”清理”方法,並明確地調用它們。finalize()還有一個又去的用法,並不依賴於每次都要對finalize進行調用,也就是對象終結條件的驗證。

    當對某個對象不再感興趣–也就是它可以被清理了,這個對象應該處於某種狀態,使它佔用的內存可以被安全地釋放。下面的例子示範了finalize()可能的使用方法:

class Book {
    boolean checkedOut = false;
    Book(boolean checkOut) {
    checkedOut = checkOut;
    }
    void checkIn() {
        checkedOut = false;
    }
    protected void finalize() {
        if(checkedOut)
            System.out.println("Error : checked out");
    }
}
public class Test {
    public static void main(String[] args) {
        Book novel = new Book(true);
        novel.checkIn();
        new Book(true);
        System.gc();
    }
}

    本例的終結條件時:所有Book對象在被當作垃圾回收前都應該被checkIn(),但是在new Book(true)這個對象沒有被checkIn,要是沒有finalize()來驗證終結條件,很難發現這種缺陷。

    System.gc()用於強制進行和終結動作。

5.5.4 垃圾回收器如何工作

    垃圾回收器對於提高對象創建速度有明顯的效果,Java虛擬機在工作的時候,存儲空間的釋放會影響存儲空間的分配,由於垃圾回收器的存在,Java從堆分配空間的速度可以和其他語言從堆棧上分配控件的速度相媲美。

    先了解其他系統中的垃圾回收機制將能幫助我們更好的理解Java中的回收機制,引用記數是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用計數器,當有引用和對象連接的時候,引用記數加1,當引用離開作用域或被置爲null時,引用記數減1。

    垃圾回收器會在含有全部對象的列表上遍歷,當發現某個對象的引用記數爲0時,就釋放其佔用的空間(但是,引用技術模式經常會在計數值變爲0的時候立即釋放對象)。如果對象之間存在循環飲用,可能會出現”對象應該被回收,但引用記數卻不爲0”的情況。引用記數常用來說明垃圾收集的工作方式,但似乎從來未被應用與任何一種Java虛擬機實現中。

    在更快的一些模式中,垃圾回收器並非基於引用記數技術,而是:對任何“活”的對象,一定能最終追溯到其存活在堆棧或靜態存儲區中的引用。如果從堆棧和靜態存儲區開始,遍歷所有的引用,就能找到所有”活”的對象,對於每個引用,必須追蹤和它關聯的對象,然後是此關聯對象的所有引用,反覆進行,直到全部被訪問。

    在上述的方式下,Java虛擬機將採用一種自適應的垃圾回收技術。其中有一種找到存活對象的方法名爲停止-複製(stop-and-copy)。顯然這意味着先暫停程序的運行(不屬於後臺回收模式),然後將所有存活的對象從當前堆複製到另外一個堆,沒有被複制的都是應當被回收的。

    當把對象從一處搬到另外一處時,所有之鄉它的那些引用都必須修正。位於堆或靜態存儲區的引用可以直接被修正,但可能還有其他指向這些對象的引用,它們在遍歷的過程中才能被找到。對於這種複製式回收器而言,效率會降低。1.需要兩個堆來回倒騰,某些Java虛擬機對此問題的處理方式是,按需從堆中分配幾塊較大的內存,複製動作發生在這些大塊內存之間。2.程序進入穩定狀態後,產生少量垃圾,但是複製式回收器還是會不停的複製,對於第二種情況,一些Java虛擬機會進行檢查:要是沒有新的垃圾產生,就會切換到另一種工作模式(標記-清掃mark-and-sweep),Sun公司早期版本的Java虛擬機使用了這種技術。對一般用途而言,”標記-清掃”方式速度相當慢,但是當只會產生少量垃圾甚至不會產生垃圾的時候,速度就很快了。

    標記-清掃 所依據的思路同樣是從堆棧和靜態存儲區出發,遍歷所有的引用,進而找出所有存活的對象。每當找到一個存活的對象,就會給對象設一個標記,這個過程中不會回收任何對象。只有全部標記工作完成的時候,清理纔會開始。在清理過程中,沒有標記的對象將全部被釋放,不會有複製動作發生。所剩下的空間是不連續的,垃圾回收器要是希望得到連續的空間的話,就得重新整理剩下的對象。

    停止-複製 的意思是這種垃圾回收機制不在後臺進行。垃圾回收動作發生的時候,程序會被停止,Sun公司的文檔中,許多參考文獻將垃圾回收視爲低優先級的後臺進程,但事實上早起Sun公司Java虛擬機中並非在後臺實現垃圾回收,而是當可用內存較少時,Sun版本的垃圾回收器會暫停運行程序,同樣的標記-清掃工作也必須在程序暫停的情況下才能進行。

    在Java虛擬機中,內存分配以較大的“塊”爲單位,嚴格來說,“停止-複製”要求在釋放舊對象之前,必須先把所有存活對象從舊堆中複製到新堆,有了“塊”之後,垃圾回收器在回收的時候可以將對象拷貝到廢棄的塊中,每個塊都用響應的代數(generation count)來記錄它是否還存活。垃圾回收器會定期進行完整的清理動作–大型對象仍然不會被複制(只是其代數會增加),內涵小型對象的塊會被複制並整理。Java虛擬機會進行監視,在“標記-清掃”和“停止-複製”之間切換,這就是“自適應”技術。

    Java虛擬機中有很多提高速度的附加技術,尤其是與加載器操作有關的,被稱爲”即時(just-in-time,JIT)”編譯器的技術。它可以把程序全部或部分翻譯成本地機器碼(這本來是Java虛擬機的工作),程序運行速度得到了提升。當需要裝在某個類時(通常是創建該類的第一個對象),編譯器會首先找到.class文件,然後將該類的字節碼裝入內存。接下來有兩種方案可供選擇:

  • 讓即時編譯器編譯所有代碼,這種家在動作散落在整個程序聲明週期內,累加起來會花費更多的時間,並且會增加可執行代碼的長度。
  • 另一種成爲惰性評估(lazy evaluation),意思是即時編譯器只在必要的時候編譯代碼,這樣不會執行的代碼不會被JIT編譯。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章