各種編程語言的實現都採用了哪些垃圾回收演算法?這些演算法都有哪些優點和缺點?


垃圾收集演算法是個很大的話題。首先要明確的是,垃圾收集演算法和語言不一定是綁定的。比如 Java,不同的 JVM 實現可能採用不同的演算法。其次,垃圾收集演算法數量龐大,一一列舉是不可能的,篇幅所限這裡只能給個非常概略的介紹。如果希望對垃圾收集相關演算法有個全景式的了解,請參閱本人的譯作,垃圾收集 (豆瓣)。

==== 轉入正文的分割線 ====

從各種垃圾收集演算法最基本的運行方式來說,大概可以分成三個類型:

1. 引用計數(reference counting):

基本思路是為每個對象加一個計數器,記錄指向這個對象的引用數量。每次有一個新的引用指向這個對象,計數器加一;反之每次有一個指向這個對象引用被置空或者指向其他對象,計數器減一。當計數器變為 0 的時候,自動刪除這個對象。

引用計數的優點是 1)相對簡單,不需要太多運行時(run-time)的支持,可以在原生不支持 GC 的語言里實現。2)對象會在成為垃圾的瞬間被釋放,不會給正常程序的執行帶來額外中斷。它的死穴是循環引用,對象 A 包含一個引用指向對象 B ,同時對象 B 包含一個引用指向對象 A,計數器就抓瞎了。另外,引用計數對正常程序的執行性能有影響(每次引用賦值都要改計數器),特別是在多線程環境下(改計數器要加鎖同步)。

現在仍然主要採用引用計數的例子有 Apple 的 ARC,C++ 新標準里的 std::shared_ptr。

2. 標記-清掃(mark-sweep)。

基本思路是先按需分配,等到沒有空閑內存的時候從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象打上標記,然後清掃一遍內存空間,把所有沒標記的對象釋放。

標記-清掃沒有無法處理循環引用的問題,不觸發 GC 時也不影響正常程序的執行性能。但它的問題是當內存耗盡觸發 GC 時,需要中斷正常程序一段時間來清掃內存,在內存大對象多的時候這個中斷可能很長。

採用或者部分採用標記-清掃的例子非常多,不一一列舉了。

3. 節點複製(copying)。

基本思路是把整個內存空間一分為二,不妨記為 A 和 B。所有對象的內存在 A 中分配,當 A 塞滿的時候,同樣從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象複製到 B 去,然後對調 A 和 B 的角色。

相對於標記-清掃,節點複製的主要缺點是總有一半空間空閑著無法利用,另一個比較隱晦的缺點是它使用內存的方式與現有的內存換頁、Cache 換入換出機制有潛在的衝突。但它有個很大的優點: 所有的對象在內存中永遠都是緊密排列的,所以分配內存的任務變得極為簡單,只要移動一個指針即可。對於內存分配頻繁的環境來說,性能優勢相當大。另外,由於不需要清掃整個內存空間,所以如果內存中存活對象很少而垃圾對象很多的話(有些語言有這個傾向),觸發 GC 造成的中斷會小於標記-清掃。

同樣的,採用或者部分採用節點複製的例子也非常多,不一一列舉了。

==== 基本演算法介紹完畢的分割線 ====

以上三種基本演算法各有優缺點,也各有許多改進的方案。目前工程實踐上最為成功的方案應該要算分代(generational)垃圾收集。它的基本思路是這樣的:程序中存在大量的臨時對象,分配出來之後很快就會被釋放,而同時如果一個對象分配出來之後相當長的一段時間內都沒回收,那麼極有可能它的生命周期很長,嘗試收集它會是無用功。所以可以把內存有意識地按「對象年齡」分成若干塊,不妨記為老中青(XD),所有的分配都在青代進行,青代塞滿只對青代做 GC,然後把存活下來的對象移動到中代,直到中青代都塞滿,再把存活下來下來的對象移動到老代 —— 這只是個思路上的例子,實踐中分代式垃圾收集演算法的方案五花八門,而且常常同時使用了不止一種基本演算法(比如青代用節點複製,老代用標記清掃啥的)。


我來回答下PHP的吧, 在5.3以前PHP採用經典的引用技術方法, 這種方法簡單, 回收期分散, 不會造成程序的長時間掛起. 然而缺點就是不能解決循環引用. 這個問題對於PHP來說本身不是什麼大問題, 因為經典的應用場景下, 一次請求結束以後, 所有屬於這個請求的內存就會被釋放. 但是隨著PHP的應用場景越來越廣, 一些後台腳本, 服務也開始需要用PHP來編寫, 這樣一來, PHP就需要解決循環引用問題.

在PHP5.3的時候, 引入了一套額外的垃圾回收演算法: &in Reference Counted Systems &> http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf

作為對引用計數的一個補充, 這套新的邏輯叫做zend_gc. 具體實現可以參看: http://www.php.net/manual/zh/features.gc.collecting-cycles.php


最近在看mruby的GC,傳統的三色收集,有趣的地方是提供了一個分代模式,基本上完全重用三色收集的基礎設施沒加幾行代碼。

(貌似從lua山寨來的?

三色收集來自dijistra的三色圖遍歷演算法,將GC的mark與sweep均攤到多次漸進片段中,雖然簡單,但其可貴之處在於能夠確保正確性。然而將一個GC周期拉得較長,內存消耗會多一些。

分代收集是歷史經驗選擇出來的優生品,可以視為最經考驗的GC演算法。"老的對象活得更長,新的對象死的快" 是基本思想,將老的對象移動到記憶集里,每一輪minor GC只mark-sweep新對象,一輪minor GC之後存活得對象就被當成老對象,慢慢得老對象會遺留得越來越多,超過某一闕值之後會觸發依次major GC,這時摘掉所有老對象的免死金牌,搞一輪大清理。相對三色GC和傳統GC的好處是GC周期縮短,內存消耗少一點。優化的實現會複雜一些?(也可以反過來說優化的空間更大)

mruby三色GC中分代模式的實現就是,黑色對象=老對象,mark階段不標記它。到觸發老對象的數量的闕值時,把所有黑對象再變回白色跑一輪完整GC。

三色GC與分代GC都需要維護一個write barrier(跟亂序執行那個內存柵欄完全是無關的兩碼事),當黑色對象(或者老對象)有添加新對象的引用時,GC必須對此有所處理。對於擴展的開發者而言,這是一個需要小心注意的地方。

lua和mruby都需要維護對象的引用關係,屬於嚴格式GC。cruby與之相對,採用了保守式GC,傳統的mark-sweep。好處是不需要write barrier,C結構體里直接引用ruby對象,提高了C擴展開發者的生活質量,不然現在絕對不會有這麼多好用的gem。缺點搞過rails的同學都懂的,而且給gc的設計者掛了個大腳鐐,以後優化起來得想更多辦法吧。

原則上保守式GC可以為C/C++提供垃圾收集的支持。按指針的寬度遍歷棧,調用 is_point_to_heap() 猜這條指針是否指向堆(就一個簡單的判斷看看這個值是不是在堆的地址的範圍之內),如果有個數值恰好跟堆里的指針的值一樣,那麼只能將哪個位置的對象mark掉了。如果碰巧這個對象是一大堆對象的根,那麼恭喜,內存會很吃虧了。

boehm對於這種情況有所研究,出了一個演算法可以減少這種誤判(書上看的,詳細不知道了)。它就只暴露了malloc()和free()兩個介面,C程序員可以很輕鬆利用它到現有項目中。為了捕獲write barrier同時不引入額外的介面,印象中它嚴重地依賴防寫和頁面錯誤。

cruby和mruby的堆是個偷懶的實現,維護兩個堆,一個對象堆,一個系統堆。ruby的所有對象的大小都相同,使得對象堆看起來有點像slab。以字元串對象為例,它會保存字元串的長度,和一個指向系統堆的字元串內容的指針,系統堆中的字元串的內容的生命周期與對象堆中的字元串對象保持一致。

ree與cruby2.0的gc對於寫時複製友好下了很大功夫,可以說這個特性這跟ruby的應用場景息息相關。在web伺服器中,fork一個進程的實例之後,如果跑一輪GC,就會寫遍所有對象,堆里所有的防寫的頁面都會失效,不得不觸發寫時複製,吃掉 (進程數*堆大小) 的物理內存。nari和matz的方案是移除對象中的標記位,統一挪到堆的頭部搞成一個bitmap。這樣在mark階段,所有的寫都會集中到這個bitmap上,原則上只會觸發少數幾次寫時複製,可以省不少內存。


從垃圾回收演算法本身而言,Erlang沒有什麼特別的,全局層面用的分代gc,語言底層用的是mark-sweep以及引用計數gc。

但是Erlang最大的特點是erlang輕量級進程的堆是完全私有的,變數是單次賦值不可變的,進程之間沒有數據共享。考慮到erlang的高並發性,擁有上萬個進程,掛起某個進程挨個來GC,其他進程不受影響,而且由於進程的堆比較小,所以可以在比較短的時間內完成。

對於超過64位元組的二進位數據,為了節省數據複製的開銷,採用引用計數的共享堆,便於數據在進程間的傳遞,所以必須當引用計數清零才能釋放數據。但是erlang的引用計數沒有循環引用的問題,因為erlang的變數是單向賦值的,只存在進程變數對二進位數據的單向引用。

從這裡可以看出,不同的數據模型對於垃圾回收的影響有多大。


感謝邀請。
JVM 最新一代的GC,Garbage First,
基本機制就是把堆劃分為很多固定大小的region,每個region都有一個對應的remembered set結構,用來記錄指向這個區域中的地址的其他區域的指針。 在垃圾回收時,選擇記錄最少的一個區域進行,按找這種方式選擇出來的區域,通常是有用數據最少、垃圾最多的區域。將其中活躍的對象通過copy的方式複製到其他的區域中(準確說來會是old region,對於old region會有一定的幾率進行回收)。這也就是「Garbage-first 」名稱的由來。如此一來就可以以region為單位整區域回收內存(傳統的GC是以單個對象內存為單位)。

下面兩篇文章都有詳盡的描述:
http://www.infoq.com/cn/news/2008/05/g1
http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html


C#中用到的標記-壓縮演算法,把可達的對象標記出來,然後回收不可達對象,最後進行內存壓縮。缺點是垃圾回收的演算法可能影響程序的性能。由於回收一部分對象比回收全部對象性能提高和越來的對象生存期就越長,所有C#採用了代的概念,既如果第0代足夠用,就不用去管第1代的對象,只回收第0代的對象。


貼一下之前的讀書筆記,拋磚引玉。
-------------------GC in python----------------------

  1. 常見的GC演算法

常見的垃圾收集(garbage collection)演算法主要有以下幾種。

  • 引用計數(Reference Count)

工作原理:
為每個內存對象維護一個引用計數。
當有新的引用指向某對象時就將該對象的引用計數加一,當指向該對象的引用被銷毀時將該計數減一,當計數歸零時,就回收該對象所佔用的內存資源。

  • 標記-清除(Mark-Sweep)

工作原理:
分兩個步驟,一是標記,即從眾多的內存對象中區分出不會再被使用的垃圾對象;二是清除,即把標記的垃圾對象清除掉。標記的時候需要確定內存對象的集合Root set,集合里的對象都是可以訪問的。如果Root set中的對象引用了其他的對象,那麼被引用的對象也不能被標記為垃圾對象。然後從Root set出發,遞歸遍歷Root set能訪問到的所有對象,進行標記為不是垃圾對象。遍歷結束後,沒有被標記的就是垃圾對象。

  • 分代收集

工作原理:
根據一個統計學上的結論,如果一個內存對象在某次Mark過程中發現不是垃圾,那麼它短期內成為垃圾的可能性就很小。分代收集將那些在多次垃圾收集過程中都沒有被標記為垃圾對象的內存對象集中到另外一個區域——年老的區域,即這個區域中的內存對象年齡比較大。因為年老區域內內存對象短期內變成垃圾的概率很低,所以這些區域的垃圾收集頻率可以降低,相對的,對年輕區域內的對象進行高頻率的垃圾收集。這樣可以提高垃圾收集的整體性能。

2. Python 中的內存管理

在CPython中,大多數對象的生命周期都是通過對象的引用計數來管理的。引用計數是一種最直觀、最簡單的垃圾收集計數,與其他主流GC演算法比較,它的最大優點是實時性,即任何內存,一旦沒有指向它的引用,就會立即被回收。
然而引用計數存在兩個比較麻煩的缺陷:

  • 當程序中出現循環引用時,引用計數無法檢測出來,被循環引用的內存對象就成了無法回收的內存,引起內存泄露。比如:

list1 = []
list1.append(list1)
del list1

list1循環引用了自身,第二行執行完後,list1的GC變成了2,執行完del操作後,list1的引用計數變為1,並沒有歸零,list1的內存空間並沒有被釋放,造成內存泄露。

  • 維護引用計數需要額外的操作。

在每次內存對象唄引用或者引用被銷毀時都需要修改引用計數,這類操作被稱為footprint。引用計數的footprint是很高的,使得程序的整體性能受到很大的影響。

3. 補充

  • 為了解決循環引用的問題,CPython特別設計了一個模塊——GC module,其主要作用就是檢查出循環引用的垃圾對象,並清除他們。該模塊的實現,實際上也是引入了前面提到的兩種主流的垃圾收集技術——標記清除和分代收集。
  • 為了解決引用計數的性能問題,盡量再內存的分配和釋放上獲得最高的效率,Python因此還設計了大量的內存池機制

[ref]《Python源碼剖析》


我來回答一下VB吧,VB其實算不上有垃圾回收演算法,至少在VB6的時代,是沒有垃圾回收這一說的,但VB確實能回收一些資源,主要對於局部變數有效,當然,用戶也可以手動釋放,另外set nothing應該算是引用計數吧。

手動釋放:
比如創建一個對象用createobject或者new關鍵字,就創建了這個對象,當用戶使用set xxx=nothing的時候就釋放了這個對象。所以VB要求全局對象必須用set xxx=nothing的方式顯式釋放。

自動釋放:
這種一般用於局部變數,比如dim了一個變數,set xx= new xxxx,即使你不釋放,函數(過程)走到頭退出的時候,它就自動釋放了。因為當你退出一個過程的時候,你申請的臨時資源都會調用析構函數,資源就釋放了。

原理么,可以理解為類的概念,其實VB就是在MFC上封裝了一層東西,內部的都是靠C的對象來維護的,那麼new一個窗體,在它關閉以後,自然會有析構函數去處理,而默認不處理的局部內容,最後都會被vb默認撤銷掉。不知道你對比過VC跟VB的代碼沒,有一個很有意思的特性,比如ADODB的recordset對象,在VB里是set rs=nothing來釋放,VC里是RS=NULL,如果單步跟蹤會發現VC里實際是重載了=這個運算符,調用的是析構函數。

當然VB垃圾回收也有不好的地方,比如,你關閉了一個窗口,如果這個窗口是工程里添加的,不是你new出來的,那麼這個窗口資源會一直存在,直到進程退出,原因還不太明白,也不知道後續會有什麼改進。http://VB.NET沒怎麼玩,改的太多了,已經不太VB了。

再補充:多個變數是可以引用同一個對象的,比如定義一堆form類型的變數,都指向某個new出來的窗體,這是可以的,即使窗體關閉了也存在,因為引用技術不為0,只有所有的變數都set =nothing以後,那個窗體資源才會被真正釋放。

總結一下:
1、通過引用計數,引用為0,自動釋放;
2、局部變數引用在失效時,自動調用set xxx=nothing;
3、全局變數引用會一直存在,需要手動調用set xxx=nothing;

就這麼多吧


Python的垃圾回收機制是通過引用計數來實現的。Python中所有的對象都有一個引用計數(Python源碼中PyObject_HEAD宏里定義的ob_refcnt變數),每當有新的地方使用該對象就會增加它的引用計數(Python源碼中的Py_XINCREF宏),反之則減少(Python源碼中的Py_XDECREF宏),當引用計數減為0的時候會被銷毀(不過相比釋放該對象的內存空間,Python更多地採用把該對象的內存空間放到對應緩衝池中去的方法)。


Luajit的維基有一篇很棒的關於垃圾收集演算法的介紹 http://wiki.luajit.org/New-Garbage-Collector#GC-Algorithms
除了引用計數(COM也是用的這個,需要手動管理)之外,還有雙色標記刪除,三色增量式,四色優化增量式,分代式的等等。演算法複雜了,不容易實現和維護,太簡單的也許在某些場景性能不夠好。
DotNet平台用的是分代式,三代,最新的4.5在GC上尤其是伺服器GC上性能有大幅改進,這個可以關注。

    • Two-Color Mark Sweep
    • Tri-Color Incremental Mark Sweep
    • Quad-Color Optimized Incremental Mark Sweep
    • Generational GC

有一本書介紹的挺詳細的:
Garbage Collection: algorithms for automatic dynamic memory management


經提醒iOS沒有傳統意義上的垃圾回收機制 ARC是編譯過程中對代碼添加一些代碼實現的內存管理的功能的

在Xcode 4.2以前 是完全沒有垃圾回收的 全部純手動 主要就是通過一些函數來管理內存的
基本概念
Object-C 的內存管理基於引用計數(Reference Count)這種非常常用的技術。簡單講,如果要使用一個對象,並希望確保在使用期間對象不被釋放,需要通過函數調用來取得「所有權」,使用結束後再調用函數釋放「所有權」。「所有權」的獲得和釋放,對應引用計數的增加和減少,為正數時代表對象還有引用,為零時代表可以釋放。
獲得所有權的函數包括

alloc – 創建對象是調用alloc,為對象分配內存,對象引用計數加一。
copy – 拷貝一個對象,返回新對象,引用計數加一。
retain – 引用計數加一,獲得對象的所有權。

釋放所有權的函數包括

release – 引用計數減一,釋放所有權。如果引用計數減到零,對象會被釋放。
autorelease – 在未來某個時機釋放。下面具體解釋。

在某些情況下,並不想取得所有權,又不希望對象被釋放。例如在一個函數中生成了一個新對象並返回,函數本身並不希望取得所有權,因為取得後再沒有機會釋放(除非創造出新的調用規則,而調用規則是一切混亂的開始),又不可能在函數內釋放,可以藉助autorelease 。所謂autorelease , 可以理解為把所有權交給一個外在的系統(這個系統實際上叫autorelease pool),由它來管理該對象的釋放。通常認為交給 autorelease 的對象在當前event loop 中都是有效的。也可以自己創建NSAutoreleasePool 來控制autorelease的過程。
據蘋果的人說,autorelease效率不高,所以能自己release的地方,盡量自己release,不要隨便交給autorelease來處理。

規則

引用計數系統有自己的引用規則,遵守規則就可以少出錯:
獲得所有權的函數要和釋放所有權的函數一一對應。
保證只有帶alloc, copy, retain 字串的函數才會讓調用者獲得所有權,也就是引用計數加一。
在對象的 dealloc函數中釋放對象所擁有的實例變數。
永遠不要直接調用dealloc來釋放對象,完全依賴引用計數來完成對象的釋放。

有很多類都提供「便利構造函數(convenience constructors)」,它們創建對象但並不增加引用計數,意味著不需要調用release來釋放所有權。很好辨認,它們的名字中不會有alloc和copy。

只要遵守這些規則,基本上可以消除所有Intrument可以發現的內存泄露問題。
容器

類似NSArray, NSDictionary, NSSet 等類,會在對象加入後引用計數加一獲得所有權,在對象被移除或者整個容器對象被釋放的時候釋放容器內對象的所有權。類似的情況還有UIView對subview的所有權關係,UINavigationController對其棧上的controller的所有權關係等等。
其他所有權的產生

還有一些用法會讓系統擁有對象的所有權。比如NSObject 的performSelector:withObject:afterDelay 。如果有必要,需要顯示的調用cancelPreviousPerformRequestsWithTarget:selector:object: ,否則有可能產生內存泄露。

因這種原因產生的泄露因為並不違反任何規則,是Intrument所無法發現的。
循環引用

所有的引用計數系統,都存在循環應用的問題。例如下面的引用關係:
對象a創建並引用到了對象b.
對象b創建並引用到了對象c.
對象c創建並引用到了對象b.

這時候b和c的引用計數分別是2和1。當a不再使用b,調用release釋放對b的所有權,因為c還引用了b,所以b的引用計數為1,b不會被釋放。b不釋放,c的引用計數就是1,c也不會被釋放。從此,b和c永遠留在內存中。

這種情況,必須打斷循環引用,通過其他規則來維護引用關係。比如,我們常見的delegate往往是assign方式的屬性而不是retain方式的屬性,賦值不會增加引用計數,就是為了防止delegation兩端產生不必要的循環引用。如果一個UITableViewController 對象a通過retain獲取了UITableView對象b的所有權,這個UITableView對象b的delegate又是a, 如果這個delegate是retain方式的,那基本上就沒有機會釋放這兩個對象了。自己在設計使用delegate模式時,也要注意這點。
以上摘自: http://www.robinlu.com/blog/archives/392
在新的Xcode 4.2 已經引入了ARC的機制
發現一篇介紹的比較好的文章 : http://blog.csdn.net/zhf198909/article/details/6954720我就不重複勞動了


縱觀GC的歷史共有三種基本演算法:
1、標記清除:

在此之上又有變形演算法——標記壓縮。
2、複製收集:
複製收集利用兩個內存區域,避免了標記清除演算法在清楚階段還需要遍歷大量死亡節點的過程,減少了時間開支。

3、引用計數
所有對象保存著自己被別人引用的次數,減少一次,則自身減少1,同時自己引用的對象的引用計數也減少1。計數為0的為死亡節點可以釋放。引用計數不需要遍歷全部對象,實現最為簡單。釋放空間是立即釋放。缺點是如果有循環引用(引用關係連成環了)則永遠也釋放不了,同時不適合併行處理。

基於以上方法現代改良的GC演算法:
1、分代回收:
基本假說:大部分新產生的對象會在較短時間內成了垃圾,而經過一定時間依然存活的對象一般壽命較長。
依據此假說,那麼只要掃描新生代的對象就可以實現大部分垃圾的回收。掃描新生代對象稱為「小回收」,一般採用複製收集演算法,另外為了加速掃描在掃描過程中如果遇到老生代對象,不遞歸掃描。然後將小回收剩下的對象划到老生代。
如果有老生代對新生代的引用,則用一個記錄集來實時記錄,稱為「寫屏障」。小回收中,記錄集也當做一個掃描「根」來對待。
一定時候會對老生代,新生代一起進行一次「大回收」。
實際上,很多語言的GC會設置好幾代,而不是兩代。
2、增量回收:
為了保持程序實時性,將一個GC不一次執行,而是分成多個小步驟在程序間隙分步執行。顯然這種演算法需要實時維護程序中對象的引用關係,在每一步驟執行時都需記錄引用狀態變更(即「寫屏障」)。
3、並行回收:
充分利用多核,讓GC並行在多核上進行回收,將佔用程序運行的時間縮小到最小。

GC大統一理論:
IBM研究者說:任何一種GC演算法都是引用計數和跟蹤回收兩種思路的組合。

——以上,整理自松本行弘《代碼的未來》

目前來看,JAVA的GC是應用技術最多,同時也是性能最好的。


推薦看一下松本行弘的《代碼的未來》,其中的 「2.4 內存管理」 對GC有較詳細的論述,
並且也對幾種不同編程語言的GC做了比較。
補一下2.4的章節目錄:

2.4 內存管理 61
看似無限的內存 61
GC的三種基本方式 62
術語定義 62
標記清除方式 63
複製收集方式 64
引用計數方式 65
引用計數方式的缺點 65
進一步改良的應用方式 66
分代回收 66
對來自老生代的引用進行記錄 67
增量回收 68
並行回收 69
GC大統一理論 69


JavaScript的:

  1. 標記清除 Mark and Swap: 為內存中存儲的變數添加標記,比如進入環境,離開環境,刪除某些標記而保留另外某些標記。
  2. 引用計數 Reference count:記錄引用的次數,被變數引用則加1,變數引用另外的值則減1,為0時可以被回收。

python的垃圾收集 參考《python學習手冊》第四版 158頁 對象的垃圾收集對@李垚 的補充
從技術上將,python的垃圾收集主要是基於引用計數器,如李垚所講,然後它也有一部分功能是可以及時地檢測並回收帶有循環引用的對象,如果你確保自己的代碼沒有產生循環引用,這個功能是可以關閉的,但是默認的該功能是可用的。


我來說一下Javascript的吧
在C和C++之類的語言,開發人員的一項基本任務就是手工跟蹤內存使用情況,內存必須手動地被釋放。
但在JS中,內存的分配及無用內存的回收實現了自動管理,這是JS的「垃圾收集」。Javascript的解釋器可以檢測到何時程序不再使用一個對象了。當它確定了一個對象時無用的時候(例如,程序中使用的變數再也無法引用這個對象了),它就知道不再需要這個對象,可以把它所佔用的內存釋放掉了。

見如下示例代碼:
var s = "hello";
var u = s.toUpperCase();
s = u;


運行了這些代碼之後,就不能再獲得原始的字元串"hello",因為程序中沒有變數再引用它了。系統檢測到這一事實後,就會釋放該字元串的存儲空間以便這些空間可以被再利用。
垃圾收集是自動進行的,對程序員來說是不可見的。對於垃圾收集,唯一需要程序員做的就是相信它一定會起作用,程序員可以創建任何想要的無用對象,系統將會將它們都清除掉。


關於JS「垃圾收集」更詳細的演算法和介紹請參考《Javascript高級程序設計(第二版)》P69-P72


各位的回答讓我很長見識!謝謝!以前只知道java的垃圾回收機制。
最近在寫C++程序,感覺到字元串資源分配管理上還不夠方便,就寫了一個公共的字元串堆資源pool管理類,由它來實現分配和回收,重點是實現回收。使用者省掉了釋放過程,這對有條件釋放的時候方很方便。


據說java是用的有向圖,優點是可以解決循環引用,缺點是效率低。


推薦一本書《垃圾收集》


推薦閱讀:

全python項目,使用protobufThrift適合嗎?
Haskell中的惰性求值如何實現?
Facebook 新發布的 Hack 語言怎麼樣?
「C++」讀作「C 加加」,為什麼「C?」不能讀作「C 井」呢?
MFC 還在更新嗎?

TAG:編程語言 | GC垃圾回收計算機科學 |