Discussion:
[重編] 談談 Java 語言的垃圾收集器
(时间太久无法回复)
痞子軍團團長
2006-08-09 06:06:45 UTC
Permalink
原文是從下面這個網址取得
http://big5.ccidnet.com:89/gate/big5/java.ccidnet.com/
art/297/20060809/790019_1.html

然後修改錯字、刪贅字、替換台灣慣用語與標點符號、重新排版而成。
大部分修改都有附上原文,以防失去替換有誤。
少數贅字 & 標點符號更換則沒有。

這篇文章好像已經沒啥版權問題?我不清楚
不過... 萬一我因此被抓去關,大家記得要來看我... [茶]

====================================================================


  [註0]

  我們知道,許多程式設計語言都允許在程式運行期動態地分配記憶體空間。
分配記憶體的方式多種多樣,取決於該種語言的語法結構。但不論是哪一種語言
的記憶體分配方式,最後都要交還 [改1] 所分配的記憶體塊的起始地址,即交
還 [改1] 一個指針到記憶體塊的首地址。

  當已經分配的記憶體空間不再需要時,換句話說當指向該記憶體塊的句柄
[註1] 超出了使用範圍的時候,該程式或其運行環境就應該回收該記憶體空間,
以節省寶貴的記憶體資源。

  在 C,C++ 或其他程式設計語言中,無論是對象 [註2] 還是動態配置的資
源或記憶體,都必須由程式員自行聲明產生和回收,否則其中的資源將消耗,造
成資源的浪費甚至當機。但手工回收記憶體往往是一項複雜而艱巨的工作。因為
要預先確定佔用的記憶體空間是否應該被回收是非常困難的!如果一段程式不能
回收記憶體空間,而且在程式運行時系統中又沒有了可以分配的記憶體空間時,
這段程式就只能崩潰。通常,我們把分配出去後,卻無法回收的記憶體空間稱為
「記憶體滲漏體(Memory Leaks)」。

  以上這種程式設計的潛在危險性在 Java 這樣以嚴謹、安全著稱的語言中是
不允許的。但是 Java 語言既不能限制 [改3] 程序員編寫程式的自由性,又不
能把聲明對象的部分去除(否則就不是物件導向 [改4] 的程式語言了),那麼
最好的解決辦法就是從 Java 程式語言本身的特性入手。於是,Java 技術提供
了一個系統級的 thread [改5],即垃圾收集器 [改6](Garbage Collection
Thread),來跟蹤每一塊分配出去的記憶體空間,當 Java 虛擬機(Java
Virtual Machine)處於空閒迴圈時,垃圾收集器會自動檢查每一塊 [改7] 分配
出去的記憶體空間,然後自動回收每一塊 [改7] 可以回收的無用的記憶體塊。

  垃圾收集器是一種低優先級的執行緒,在一個 Java 程式的生命週期中,它
只有在 CPU [改8] 空閒的時候才有機會運行。它有效地防止了 memory leaks
[改9] 的出現,並盡可能 [改10] 地節省了寶貴的記憶體資源。但是,通過 JVM
[改11] 來執行垃圾收集器的方案可以是多種多樣的。

  下面介紹垃圾收集器的特點和它的執行機制:

  垃圾收集器系統有自己的一套方案來判斷哪個記憶體塊是應該被回收的、哪
個是不符合要求暫不回收的。垃圾收集器在一個 Java 程式中是自動執行的
[改12],不能強制執行。即使程式員能明確地判斷出有一塊記憶體已經無用了、
是應該回收的,程式員也不能強制垃圾收集器回收該記憶體塊。程式員唯一能做
的就是通過調用 [改13] 方法來「建議」執行垃圾收集器;但其是否可以執行,
什麼時候執行卻都是不可知的。這也是垃圾收集器的最主要的缺點。當然相對於
它給程式員帶來的巨大方便性而言,這個缺點是瑕不掩瑜的。

  垃圾收集器的主要特點有:

  1. 垃圾收集器的工作目標是回收已經無用的對象的記憶體空間,從而避免
    memory leaks 產生、節省記憶體資源、避免程式代碼的崩潰。
  2. 垃圾收集器判斷一個對象的記憶體空間是否無用的標準是:如果該對象
    不再被 [改14] 程式中任何一個「活動的部分」所引用,此時我們就說
    ,該對象的記憶體空間已經無用。所謂「活動的部分」,是指程式中某
    部分參與程式的調用,正在執行過程中,尚未執行完畢。
  3. 垃圾收集器雖然是低優先等級的 thread [改15],但在系統可用記憶體
    量過低的時候,它可能會突發地執行來挽救記憶體資源。當然其執行與
    否也是不可預知的。
  4. 垃圾收集器不可以被強制執行,但程式員可以通過調用 System.gc() 方
    法來建議執行垃圾收集器。
  5. 不能保證一個無用的對象一定會被垃圾收集器收集,也不能保證垃圾收
    集器在一段 Java 語言代碼中一定會執行。因此在程式執行過程中被分
    配出去的記憶體空間可能會一直保留到該程式執行完畢,除非該空間被
    重新分配或被其他方法回收。由此可見,完全徹底地根絕 memory leaks
    的產生也是不可能的。但是請不要忘記,Java 的垃圾收集器畢竟使程式
    員從手工回收記憶體空間的繁重工作中解脫了出來。設想一個程式員要
    用 C 或 C++ 來編寫一段 10 萬行語句的程式碼 [改16],那麼他一定會
    充分體會到 Java 垃圾收集器的優點!
  6. 同樣沒有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪一個
    會被首先收集。
  7. 迴圈引用對象不會影響其被垃圾收集器收集。
  8. 可以通過將對象的引用變數(reference variables,即句柄 handles)
    設定為 null [改17],來暗示垃圾收集器來收集該對象。但此時,如果
    該對象連接有事件監聽器(典型的 AWT 組件),那它還是不可以被收集
    。所以在設一個引用變數為 null 值之前,應注意該引用變數指向的對
    像是否被監聽,若有,要首先除去監聽器,然後才可以賦空值。
  9. 每一個對象都有一個 finalize() 方法,這個方法是從 Object 類繼承
    來的。
  10. finalize() 方法用來回收記憶體以外的系統資源,就像是文件處理器
    和網路連接器。該方法的調用順序和用來調用該方法的對象的創建順序
    是無關的。換句話說,書寫程式時該方法的順序和方法的實際調用順序
    是不相干的。請注意這只是 finalize() 方法的特點。
  11. 每個對象只能調用 finalize() 方法一次。如果在 finalize() 方法執
    行時產生異常(exception),則該對象仍可以被垃圾收集器收集。
  12. 垃圾收集器跟蹤每一個對象,收集那些不可到達的對象(即該對象沒有
    被程式的任何「活的部分」所調用),回收其佔有的記憶體空間。但在
    進行垃圾收集的時候,垃圾收集器會調用 finalize() 方法,通過讓其
    他對象知道它的存在,而使不可到達的對象再次「復蘇」為可到達的對
    象。既然每個對象只能調用一次 finalize() 方法,所以每個對象也只
    可能「復蘇」一次。
  13. finalize() 方法可以明確地被調用,但它卻不能進行垃圾收集。
  14. finalize() 方法可以被重載(overload),但只有具備初始的
    finalize() 方法特點的方法才可以被垃圾收集器調用。
  15. 子類別 [改18] 的 finalize() 方法可以明確地調用父類別 [改18] 的
    finalize() 方法,作為該子類對象的最後一次適當的操作。但 Java
    編譯器卻不認為這是一次覆蓋操作(overriding),所以也不會對其調
    用進行檢查。
  16. 當 finalize() 方法尚未被調用時,System.runFinalization() 方法
    可以用來調用 finalize() 方法,並實現相同的效果,對無用對象進行
    垃圾收集。
  17. 當一個方法 [註3] 執行完畢,其中的局部變數就會超出使用範圍,此
    時可以被當作垃圾收集。但以後每當該方法再次被調用時,其中的局部
    變數便會被重新創建。
  18. Java 語言使用了一種「標記交換區的垃圾收集演算法」。該演算法會
    遍歷程式中每一個對象的句柄,為被引用的對象做標記,然後回收尚未
    做標記的對象。所謂遍歷可以簡單地理解為「檢查每一個」。 [註4]
  19. Java 語言允許程式員為任何方法添加 finalize() 方法,該方法會在
    垃圾收集器交換回收對象之前被調用。但不要過分依賴該方法對系統資
    源進行回收和再利用,因為該方法調用後的執行結果是不可預知的。

  通過以上對垃圾收集器特點的了解,你應該可以明確垃圾收集器的作用,和
垃圾收集器判斷一塊記憶體空間是否無用的標準。簡單地說,當你為一個對象賦
值為 null 並且重新定向了該對象的引用者,此時該對象就符合垃圾收集器的收
集標準。

  判斷一個對像是否符合垃圾收集器的收集標準,這是SUN公司程式員認證考
試中垃圾收集器部分的重要考點(可以說,這是唯一的考點)。所以,考生在一
段給定的代碼中,應該能夠判斷出哪個對象符合垃圾收集器收集的標準,哪個不
符合。下面結合幾種認證考試中可能出現的題型來具體講解:

  Object obj = new Object ();

  我們知道,obj 為 Object 的一個句柄。當出現 new 關鍵字時,就給新建
的對象分配記憶體空間,而 obj 的值就是新分配的記憶體空間的首地址,即該
對象的值(請特別注意,對象的值和對象的內容是不同含義的兩個概念:對象的
值就是指其記憶體塊的首地址,即對象的句柄;而對象的內容則是其具體的記憶
體塊)。此時如果有 obj = null;則 obj 指向的記憶體塊此時就無用了,因為
下面再沒有調用該變數了。

  請再看以下三種認證考試時可能出現的題型:
  
  程式段1:
  1. fobj = new Object ();
  2. fobj.Method();
  3. fobj = new Object( ) ;
  4. fobj.Method( ) ;

  問:這段代碼中,第幾行的 fobj 符合垃圾收集器的收集標準?
  答:第 3 行。因為第 3 行的 fobj 被賦了新值,產生了一個新的對象,即
換了一塊新的記憶體空間,也相當於為第 1 行中的 fobj 賦了 null 值。這種
類型的題在認證考試中是最簡單的。

  程式段2:
  1. Object sobj = new Object ();
  2. Object sobj = null;
  3. Object sobj = new Object ();
  4. sobj = new Object ();

  問:這段代碼中,第幾行的記憶體空間符合垃圾收集器的收集標準?
  答:第 1 行和第 3 行。因為第 2 行為 sobj 賦值為 null,所以在此第
1 行的 sobj 符合垃圾收集器的收集標準。而第 4 行相當於為 sobj 賦值為
null,所以在此第 3 行的 sobj 也符合垃圾收集器的收集標準。

  如果有一個對象的句柄 a,且你把 a 作為某個構造器的參數,即 new
Constructor(a) 的時候,即使你給 a 賦值為 null,a 也不符合垃圾收集器的
收集標準。直到由上面構造器構造的新對象被賦空值時,a 才可以被垃圾收集器
收集。

  程式段3:
  1. Object aobj = new Object ();
  2. Object bobj = new Object ();
  3. Object cobj = new Object ();
  4. aobj = bobj;
  5. aobj = cobj;
  6. cobj = null;
  7. aobj = null;

  問:這段代碼中,第幾行的記憶體空間符合垃圾收集器的收集標準?
  答:第 7 行。注意這類題型是認證考試中可能遇到的最難題型了。
  行 1~3 分別創建了 Object 類的三個對象:aobj,bobj,cobj
  行 4:此時對象 aobj 的句柄指向 bobj,所以該行的執行不能使 aobj 符
合垃圾收集器的收集標準。
  行 5:此時對象 aobj 的句柄指向 cobj,所以該行的執行不能使 aobj 符
合垃圾收集器的收集標準。
  行 6:此時仍沒有任何一個對象符合垃圾收集器的收集標準。
  行 7:對象 cobj 符合了垃圾收集器的收集標準,因為 cobj 的句柄指向單
一的地址空間。在第 6 行的時候,cobj 已經被賦值為 null,但由 cobj 同時
還指向了 aobj(第 5 行),所以此時 cobj 並不符合垃圾收集器的收集標準。
而在第 7 行,aobj 所指向的地址空間也被賦予了空值 null,這就說明瞭,由
cobj 所指向的地址空間已經被完全地賦予了空值。所以此時 cobj 最終符合了
垃圾收集器的收集標準。但對於 aobj 和 bobj,仍然無法判斷其是否符合收集
標準。

  總之,在Java語言中,判斷一塊記憶體空間是否符合垃圾收集器收集標準的
標準只有兩個:
  1. 給對象賦予了空值null,以下再沒有調用過。
  2. 給對象賦予了新值,既重新分配了記憶體空間。

  最後再次提醒一下,一塊記憶體空間符合了垃圾收集器的收集標準,並不意
味著這塊記憶體空間就一定會被垃圾收集器收集。

==========================================================================
修改部份
1. 返回→交還
2. 死機→當機
3. 限製→限制
4. 面向對象→物件導向
5. 線程→ Thread(其後不再註明)
6. 垃圾收集器線程→垃圾收集器(其後不再註明)
7. 每一快→每一塊
8. 記憶體→CPU
9. 記憶體滲漏體→memory leaks(其後不再註明)
10. 極大可能→盡可能
11. Java虛擬機→JVM(其後不再註明)
12. 的執行是自動的→是自動執行的
13. System. gc→System.gc()(其後不再註明)
14. 不能再被→不再被
15. 作為低優先級的線程運行→低優先等級的 thread
16. 代碼→程式碼(其後不再註明)
17. 初始化為null值→設定為 null
18. 類→類別

註解部份
0. 把第一段的廢話砍掉
1. handles, reference variables
2. 應該是 reference
(上述兩個,如果有更好的中文翻譯,懇請賜教)
3. 應該是 method
4. 詳細演算法內容可參考下列網址
http://www.microsoft.com/taiwan/msdn/columns/DoNet/garbage_collection.htm

--
 侃侃長論鮮窒礙  網站:http://www.psmonkey.idv.tw
 眾目睽睽無心顫  個人版:telnet://legend.twbbs.org
 煢居少聊常人事 
 殺頭容易告白難  歡迎參觀 Java 版(@ptt.cc)精華區 \囧/

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.126.172.167
※ 編輯: PsMonkey 來自: 59.126.172.167 (08/09 22:06)
Test
2006-08-09 09:43:17 UTC
Permalink
其實就我看了一堆程式語言的結果是...
我覺得 Garbage Collection 還算蠻普及的....

應該這麼說好了...支援 Garbage Collection 的語言, 就我看過的...
好像算一下都有支援 Garbage Collection @_@

像是 D, Ruby(及其他 Script 語言如 Perl, Python...),
Smalltalk 類語言(Squeak 等等), Lisp 系(Common Lisp, Scheme)
Functional 語言(ML, Haskell)....感覺上好像都是支援 Garbage Collection @_@

接下來微軟的平台 .Net (C#.Net, VB.Net, 這些也是有 Garbage Collection)

我想應該是, 其實以前就有 Garbage Collection, 可是在 Java 出來之前,
大部份人只學了主流的語言如 C, C++ 等等...
所以 Java 出來時, 就覺得 Java 很多理念蠻新的...
可是如果再往前看, 其實再早以前就有 Garbage Collection 了...
應該說看了也漸漸變成見怪不怪了 @_@

其實除了 Java 的 Garbage Collection 之外...
D 的 Garbage Collection 可以設成手動, 半手動, 全自動...
( 就是可以完全關掉 Garbage Collection, 也可以手動 free 記憶體 )
和 .Net 的 IDisposable 的可以手動清除一些元件
這兩個的設計我是覺得比 Java 好就是了 :QQ


--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 221.169.205.162
._.
2006-08-09 14:36:30 UTC
Permalink
http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)

Garbage collection was invented by John McCarthy around 1959 to
solve the problems of manual memory management in his recently
devised Lisp programming language.

老兄, 現在 2006 年了. 中間隔了快 50 年了.

你要是對追朔歷史很有興趣的話,

http://www.oreilly.com/news/graphics/prog_lang_poster.pdf

對了, 這張圖好像到 2003 年而已, 恕我眼拙,
你有發現 D 在哪的話, 跟我說一聲, 我找不太到啊.

像那個 .Net 的 C# 好像也是 2000 年以後才出現的東西阿.

你說的某些語言要不就是比 Java 晚生,

要不就是比 Java 晚加掛那些新理念.

所以 Java 出來時 (1995年), 會覺得 Java 很多理念蠻新的,

這也是沒有辦法的事情嘛.

※ 引述《Schelfaniel (Test)》之銘言:
: 其實就我看了一堆程式語言的結果是...
: 我覺得 Garbage Collection 還算蠻普及的....
: 應該這麼說好了...支援 Garbage Collection 的語言, 就我看過的...
: 好像算一下都有支援 Garbage Collection @_@
: 像是 D, Ruby(及其他 Script 語言如 Perl, Python...),
: Smalltalk 類語言(Squeak 等等), Lisp 系(Common Lisp, Scheme)
: Functional 語言(ML, Haskell)....感覺上好像都是支援 Garbage Collection @_@
: 接下來微軟的平台 .Net (C#.Net, VB.Net, 這些也是有 Garbage Collection)
: 我想應該是, 其實以前就有 Garbage Collection, 可是在 Java 出來之前,
: 大部份人只學了主流的語言如 C, C++ 等等...
: 所以 Java 出來時, 就覺得 Java 很多理念蠻新的...
: 可是如果再往前看, 其實再早以前就有 Garbage Collection 了...
: 應該說看了也漸漸變成見怪不怪了 @_@
: 其實除了 Java 的 Garbage Collection 之外...
: D 的 Garbage Collection 可以設成手動, 半手動, 全自動...
: ( 就是可以完全關掉 Garbage Collection, 也可以手動 free 記憶體 )
: 和 .Net 的 IDisposable 的可以手動清除一些元件
: 這兩個的設計我是覺得比 Java 好就是了 :QQ

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 163.25.148.49
Test
2006-08-09 16:55:55 UTC
Permalink
※ 引述《ogamenewbie (._.)》之銘言:
: 對了, 這張圖好像到 2003 年而已, 恕我眼拙,
: 你有發現 D 在哪的話, 跟我說一聲, 我找不太到啊.
: 像那個 .Net 的 C# 好像也是 2000 年以後才出現的東西阿.
: 你說的某些語言要不就是比 Java 晚生,
: 要不就是比 Java 晚加掛那些新理念.
: 所以 Java 出來時 (1995年), 會覺得 Java 很多理念蠻新的,
: 這也是沒有辦法的事情嘛.
基本上, 我並是特別真的對這個 Garbage Collection 有興趣...
我只是想想我自己目前會的語言中...其實支援 Garbage Collection 的還不少...
會有一些比 Java 晚生的也蠻合理的...( 當然也有一些是比 Java 早的 )

一開始 Java 剛出時, 其實有點搞不清楚狀況...
對 Garbage Collection 這概念覺得蠻新奇的...( 那時也沒有 Wiki )
但現在來說, 總覺得這個已經算是很普及的技術了...
連 C++ 0x 據說都會考慮 Garbage Collection ( 或是說已經加入了 )

可是, 其實 Java 的 Garbage Collection, 應該說還是不是原創的..
JVM 也是...這些概念, 在更早的語言就有了...
只是以 Java 的普及度, 讓 Garbage Collection 比較浮上檯面的感覺 @_@

我覺得 AJAX 也算是類似的技術吧, 早在 google 使用 AJAX 之前就有 AJAX 了..
但是等 google 使用時, 這技術才比較浮上檯面...到現在的大量普及...
我自己是希望, 能在一個技術普及之前, 就先去瞭解它, 使用它...
當然希望國人也多一點這種人啦...因為目前這些技術給我的感覺是...
這些東西並不是原創, 應該是說, 原創到普及, 中間有一段距離,
往往是根據某個契機才普及的....

接下來普及之後, 就會有一瘋蜂的跟隨者, 然後就變成大眾也不得去學了...
結論是 : 其實一開始原創的技術, 也許不錯, 但打不開市場的話, 就會被人淡忘
直到有一個情形, 就是有人加以使用, 把市場打開, 接下來大家再投入下去...
雖然說大家都有貢獻, 但是最會被人記得的是, 中間打開市場的那個...

打開市場的之前的, 一般人會說沒聽過, 不知道, 最後就是被人淡忘
打開市場的之後的, 一般人會說, 其實它也只不過抄襲模仿打開市場那一個

這樣來說的話, 打開市場的那一個功勞變成最大了?
我總覺得不應該是這樣, 但目前時代好像就是一旦成為主流的,
接下來很多功勞都直接變成它的了 @_@
( 註: 這邊不是特別指 Java 啦...
如果換一個語言有類似的情形, 應該還是會出現類似的情形吧 )

D 呀, 如果我沒記錯的話, 到目前為止約 5 年了, 大概在 2000, 2001
左右出來的, 之前看語言使用統計的網站, 大約在 20 名左右吧...
那時是和 Ruby 相當說...後來它官方留言板連不上我就沒繼續碰了
現在我沒查就不太清楚了 @_@
我是覺得 D 也不太可能像 Java 這樣普及啦...

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 221.169.205.162
l***@alexbbs.twbbs.org
2006-08-11 15:47:38 UTC
Permalink
※ 引述《***@ptt.cc (痞子軍團團長)》之銘言:
:   程式段3:
:   1. Object aobj = new Object ();
:   2. Object bobj = new Object ();
:   3. Object cobj = new Object ();
:   4. aobj = bobj;
:   5. aobj = cobj;
:   6. cobj = null;
:   7. aobj = null;
:   問:這段代碼中,第幾行的記憶體空間符合垃圾收集器的收集標準?
:   答:第 7 行。注意這類題型是認證考試中可能遇到的最難題型了。
:   行 1~3 分別創建了 Object 類的三個對象:aobj,bobj,cobj
:   行 4:此時對象 aobj 的句柄指向 bobj,所以該行的執行不能使 aobj 符
: 合垃圾收集器的收集標準。

錯的, 此時aobj原來的instance已經沒人存取得到了, 所以會被gc掉
經程式驗證過, 如下: (為方便大家複製貼上, 刻意壓縮行數, 排版較難看些)

public class T {
String name;
public T(String n) { name = n; }
protected void finalize() { System.out.println (name + " was GCed."); }
public static void main(String args[]) {
T a = new T("a");
System.out.println ("line 1: "); System.gc();
T b = new T("b");
System.out.println ("line 2: "); System.gc();
T c = new T("c");
System.out.println ("line 3: "); System.gc();
a = b;
System.out.println ("line 4: "); System.gc();
a = c;
System.out.println ("line 5: "); System.gc();
c = null;
System.out.println ("line 6: "); System.gc();
a = null;
System.out.println ("line 7: "); System.gc();
}
}

執行結果:
line 1:
line 2:
line 3:
line 4:
a was GCed.
line 5:
line 6:
line 7:
c was GCed.



:   行 5:此時對象 aobj 的句柄指向 cobj,所以該行的執行不能使 aobj 符
: 合垃圾收集器的收集標準。
:   行 6:此時仍沒有任何一個對象符合垃圾收集器的收集標準。
:   行 7:對象 cobj 符合了垃圾收集器的收集標準,因為 cobj 的句柄指向單
: 一的地址空間。在第 6 行的時候,cobj 已經被賦值為 null,但由 cobj 同時
: 還指向了 aobj(第 5 行),所以此時 cobj 並不符合垃圾收集器的收集標準。
: 而在第 7 行,aobj 所指向的地址空間也被賦予了空值 null,這就說明瞭,由
: cobj 所指向的地址空間已經被完全地賦予了空值。所以此時 cobj 最終符合了
: 垃圾收集器的收集標準。但對於 aobj 和 bobj,仍然無法判斷其是否符合收集
: 標準。

--
※Post by leon from 59-115-227-228.dynamic.h
老鼠的香香乳酪洞˙電子佈告欄系統˙alexbbs.twbbs.org˙140.113.166.7
Loading...