我們知道,許多程序設計語(yǔ)言都允許在程序運行期動(dòng)態(tài)地分配內存空間。分配內存的方式多種多樣,取決于該種語(yǔ)言的語(yǔ)法結構。但不論是哪一種語(yǔ)言的內存分配方式,最后都要返回所分配的內存塊的起始地址,即返回一個(gè)指針到內存塊的首地址。
當已經(jīng)分配的內存空間不再需要時(shí),換句話(huà)說(shuō)當指向該內存塊的句柄超出了使用范圍的時(shí)候,該程序或其運行環(huán)境就應該回收該內存空間,以節省寶貴的內存資源。
在 C , C++ 或其他程序設計語(yǔ)言中,無(wú)論是對象還是動(dòng)態(tài)配置的資源或內存,都必須由程序員自行聲明產(chǎn)生和回收,否則其中的資源將消耗,造成資源的浪費甚至死機。但手工回收內存往往是一項復雜而艱巨的工作。因為要預先確定占用的內存空間是否應該被回收是非常困難的!如果一段程序不能回收內存空間,而且在程序運行時(shí)系統中又沒(méi)有了可以分配的內存空間時(shí),這段程序就只能崩潰。通常,我們把分配出去后,卻無(wú)法回收的內存空間稱(chēng)為 " 內存滲漏體( Memory Leaks ) " 。
以上這種程序設計的潛在危險性在 Java 這樣以嚴謹、安全著(zhù)稱(chēng)的語(yǔ)言中是不允許的。但是 Java 語(yǔ)言既不能限制程序員編寫(xiě)程序的自由性,又不能把聲明對象的部分去除(否則就不是面向對象的程序語(yǔ)言了),那么最好的解決辦法就是從 Java 程序語(yǔ)言本身的特性入手。于是, Java 技術(shù)提供了一個(gè)系統級的線(xiàn)程( Thread ),即垃圾收集器線(xiàn)程( Garbage Collection Thread ),來(lái)跟蹤每一塊分配出去的內存空間,當 Java 虛擬機( Java Virtual Machine )處于空閑循環(huán)時(shí),垃圾收集器線(xiàn)程會(huì )自動(dòng)檢查每一快分配出去的內存空間,然后自動(dòng)回收每一快可以回收的無(wú)用的內存塊。
垃圾收集器線(xiàn)程是一種低優(yōu)先級的線(xiàn)程,在一個(gè) Java 程序的生命周期中,它只有在內存空閑的時(shí)候才有機會(huì )運行。它有效地防止了內存滲漏體的出現,并極大可能地節省了寶貴的內存資源。但是,通過(guò) Java 虛擬機來(lái)執行垃圾收集器的方案可以是多種多樣的。
下面介紹垃圾收集器的特點(diǎn)和它的執行機制: 垃圾收集器系統有自己的一套方案來(lái)判斷哪個(gè)內存塊是應該被回收的,哪個(gè)是不符合要求暫不回收的。垃圾收集器在一個(gè) Java 程序中的執行是自動(dòng)的,不能強制執行,即使程序員能明確地判斷出有一塊內存已經(jīng)無(wú)用了,是應該回收的,程序員也不能強制垃圾收集器回收該內存塊。程序員唯一能做的就是通過(guò)調用 System. gc 方法來(lái) " 建議 " 執行垃圾收集器,但其是否可以執行,什么時(shí)候執行卻都是不可知的。這也是垃圾收集器的最主要的缺點(diǎn)。當然相對于它給程序員帶來(lái)的巨大方便性而言,這個(gè)缺點(diǎn)是瑕不掩瑜的。 垃圾收集器的主要特點(diǎn)有: 1 .垃圾收集器的工作目標是回收已經(jīng)無(wú)用的對象的內存空間,從而避免內存滲漏體的產(chǎn)生,節省內存資源,避免程序代碼的崩潰。
2 .垃圾收集器判斷一個(gè)對象的內存空間是否無(wú)用的標準是:如果該對象不能再被程序中任何一個(gè) " 活動(dòng)的部分 " 所引用,此時(shí)我們就說(shuō),該對象的內存空間已經(jīng)無(wú)用。所謂 " 活動(dòng)的部分 " ,是指程序中某部分參與程序的調用,正在執行過(guò)程中,尚未執行完畢。
3 .垃圾收集器線(xiàn)程雖然是作為低優(yōu)先級的線(xiàn)程運行,但在系統可用內存量過(guò)低的時(shí)候,它可能會(huì )突發(fā)地執行來(lái)挽救內存資源。當然其執行與否也是不可預知的。
4 .垃圾收集器不可以被強制執行,但程序員可以通過(guò)調用 System. gc 方法來(lái)建議執行垃圾收集器。
5 .不能保證一個(gè)無(wú)用的對象一定會(huì )被垃圾收集器收集,也不能保證垃圾收集器在一段 Java 語(yǔ)言代碼中一定會(huì )執行。因此在程序執行過(guò)程中被分配出去的內存空間可能會(huì )一直保留到該程序執行完畢,除非該空間被重新分配或被其他方法回收。由此可見(jiàn),完全徹底地根絕內存滲漏體的產(chǎn)生也是不可能的。但是請不要忘記, Java 的垃圾收集器畢竟使程序員從手工回收內存空間的繁重工作中解脫了出來(lái)。設想一個(gè)程序員要用 C 或 C++ 來(lái)編寫(xiě)一段 10 萬(wàn)行語(yǔ)句的代碼,那么他一定會(huì )充分體會(huì )到 Java 的垃圾收集器的優(yōu)點(diǎn)!
6 .同樣沒(méi)有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪一個(gè)會(huì )被首先收集。
7 .循環(huán)引用對象不會(huì )影響其被垃圾收集器收集。
8 .可以通過(guò)將對象的引用變量( reference variables ,即句柄 handles )初始化為 null 值,來(lái)暗示垃圾收集器來(lái)收集該對象。但此時(shí),如果該對象連接有事件監聽(tīng)器(典型的 AWT 組件),那它還是不可以被收集。所以在設一個(gè)引用變量為 null 值之前,應注意該引用變量指向的對象是否被監聽(tīng),若有,要首先除去監聽(tīng)器,然后才可以賦空值。
9 .每一個(gè)對象都有一個(gè) finalize( ) 方法,這個(gè)方法是從 Object 類(lèi)繼承來(lái)的。
10 . finalize( ) 方法用來(lái)回收內存以外的系統資源,就像是文件處理器和網(wǎng)絡(luò )連接器。該方法的調用順序和用來(lái)調用該方法的對象的創(chuàng )建順序是無(wú)關(guān)的。換句話(huà)說(shuō),書(shū)寫(xiě)程序時(shí)該方法的順序和方法的實(shí)際調用順序是不相干的。請注意這只是 finalize( ) 方法的特點(diǎn)。
11 .每個(gè)對象只能調用 finalize( ) 方法一次。如果在 finalize( ) 方法執行時(shí)產(chǎn)生異常( exception ),則該對象仍可以被垃圾收集器收集。
12 .垃圾收集器跟蹤每一個(gè)對象,收集那些不可到達的對象(即該對象沒(méi)有被程序的任何 " 活的部分 " 所調用),回收其占有的內存空間。但在進(jìn)行垃圾收集的時(shí)候,垃圾收集器會(huì )調用 finalize( ) 方法,通過(guò)讓其他對象知道它的存在,而使不可到達的對象再次 " 復蘇 " 為可到達的對象。既然每個(gè)對象只能調用一次 finalize( ) 方法,所以每個(gè)對象也只可能 " 復蘇 " 一次。
13 . finalize( ) 方法可以明確地被調用,但它卻不能進(jìn)行垃圾收集。
14 . finalize( ) 方法可以被重載( overload ),但只有具備初始的 finalize( ) 方法特點(diǎn)的方法才可以被垃圾收集器調用。
15 .子類(lèi)的 finalize( ) 方法可以明確地調用父類(lèi)的 finalize( ) 方法,作為該子類(lèi)對象的最后一次適當的操作。但 Java 編譯器卻不認為這是一次覆蓋操作( overriding ),所以也不會(huì )對其調用進(jìn)行檢查。
16 .當 finalize( ) 方法尚未被調用時(shí), System. runFinalization( ) 方法可以用來(lái)調用 finalize( ) 方法,并實(shí)現相同的效果,對無(wú)用對象進(jìn)行垃圾收集。
17 .當一個(gè)方法執行完畢,其中的局部變量就會(huì )超出使用范圍,此時(shí)可以被當作垃圾收集,但以后每當該方法再次被調用時(shí),其中的局部變量便會(huì )被重新創(chuàng )建。
18 . Java 語(yǔ)言使用了一種 " 標記交換區的垃圾收集算法 " 。該算法會(huì )遍歷程序中每一個(gè)對象的句柄,為被引用的對象做標記,然后回收尚未做標記的對象。所謂遍歷可以簡(jiǎn)單地理解為 " 檢查每一個(gè) " 。
19 . Java 語(yǔ)言允許程序員為任何方法添加 finalize( ) 方法,該方法會(huì )在垃圾收集器交換回收對象之前被調用。但不要過(guò)分依賴(lài)該方法對系統資源進(jìn)行回收和再利用,因為該方法調用后的執行結果是不可預知的。
通過(guò)以上對垃圾收集器特點(diǎn)的了解,你應該可以明確垃圾收集器的作用,和垃圾收集器判斷一塊內存空間是否無(wú)用的標準。簡(jiǎn)單地說(shuō),當你為一個(gè)對象賦值為 null 并且重新定向了該對象的引用,此時(shí)該對象就符合垃圾收集器的收集標準。
判斷一個(gè)對象是否符合垃圾收集器的收集標準,這是 SUN 公司程序員認證考試中垃圾收集器部分的重要考點(diǎn)(可以說(shuō),這是唯一的考點(diǎn))。所以,考生在一段給定的代碼中,應該能夠判斷出哪個(gè)對象符合垃圾收集器收集的標準,哪個(gè)不符合。下面結合幾種認證考試中可能出現的題型來(lái)具體講解:
Object obj = new Object ( ) ; 我們知道, obj 為 Object 的一個(gè)句柄。當出現 new 關(guān)鍵字時(shí),就給新建的對象分配內存空間,而 obj 的值就是新分配的內存空間的首地址,即該對象的值 ( 請特別注意,對象的值和對象的內容是不同含義的兩個(gè)概念:對象的值就是指其內存塊的首地址,即對象的句柄;而對象的內容則是其具體的內存塊 ) 。此時(shí)如果有 obj = null ; 則 obj 指向的內存塊此時(shí)就無(wú)用了,因為下面再沒(méi)有調用該變量了。 請再看以下三種認證考試時(shí)可能出現的題型:
程序段 1: 1.fobj = new Object ( ) ; 2.fobj. Method ( ) ; 3.fobj = new Object ( ) ; 4.fobj. Method ( ) ; 問(wèn):這段代碼中,第幾行的fobj 符合垃圾收集器的收集標準? 答:第1行。因為第3行的fobj被賦了新值,產(chǎn)生了一個(gè)新的對象,即換了一塊新的內存空間,也相當于為第1行中的fobj賦了null值。這種類(lèi)型的題在認證0考試中是最簡(jiǎn)單的。 程序段2: 1.Object sobj = new Object ( ) ; 2.Object sobj = null ; 3.Object sobj = new Object ( ) ; 4.sobj = new Object ( ) ; 問(wèn):這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準? 答:第1行和第3行。因為第2行為sobj賦值為null,所以在此第1行的sobj符合垃圾收集器的收集標準。而第4行相當于為sobj賦值為null,所以在此第3行的sobj也符合垃圾收集器的收集標準。 如果有一個(gè)對象的句柄a,且你把a作為某個(gè)構造器的參數,即 new Constructor ( a )的時(shí)候,即使你給a賦值為null,a也不符合垃圾收集器的收集標準。直到由上面構造器構造的新對象被賦空值時(shí),a才可以被垃圾收集器收集。
程序段3: 1.Object aobj = new Object ( ) ; 2.Object bobj = new Object ( ) ; 3.Object cobj = new Object ( ) ; 4.a(chǎn)obj = bobj; 5.a(chǎn)obj = cobj; 6.cobj = null; 7.a(chǎn)obj = null; 問(wèn):這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準? 答:第7行。注意這類(lèi)題型是認證考試中可能遇到的最難題型了。 行1-3分別創(chuàng )建了Object類(lèi)的三個(gè)對象:aobj,bobj,cobj 行4:此時(shí)對象aobj的句柄指向bobj,所以該行的執行不能使aobj符合垃圾收集器的收集標準。 行5:此時(shí)對象aobj的句柄指向cobj,所以該行的執行不能使aobj符合垃圾收集器的收集標準。 行6:此時(shí)仍沒(méi)有任何一個(gè)對象符合垃圾收集器的收集標準。 行7:對象cobj符合了垃圾收集器的收集標準,因為cobj的句柄指向單一的地址空間。在第6行的時(shí)候,cobj已經(jīng)被賦值為null,但由cobj同時(shí)還指向了aobj(第5行),所以此時(shí)cobj并不符合垃圾收集器的收集標準。而在第7行,aobj所指向的地址空間也被賦予了空值null,這就說(shuō)明了,由cobj所指向的地址空間已經(jīng)被完全地賦予了空值。所以此時(shí)cobj最終符合了垃圾收集器的收集標準。 但對于aobj和bobj,仍然無(wú)法判斷其是否符合收集標準。 總之,在Java語(yǔ)言中,判斷一塊內存空間是否符合垃圾收集器收集標準的標準只有兩個(gè): 1.給對象賦予了空值null,以下再沒(méi)有調用過(guò)。 2.給對象賦予了新值,既重新分配了內存空間。 最后再次提醒一下,一塊內存空間符合了垃圾收集器的收集標準,并不意味著(zhù)這塊內存空間就一定會(huì )被垃圾收集器收集
|