Flash务实主义(五)——AS3的垃圾回收(转)
原文地址:http://www.infoq.com/cn/news/2011/05/flash-pragmatism-5-as3-gc
垃圾回收,這次是一個被無數人討論過的傳統話題。
Action Script使用的是和Java相似的內存管理機制,并不會即時回收廢棄對象的內存,而是在特定時間統一執行一次GC(Gabage Collection)操作來釋放廢棄對象的內存,避免了重復判斷是否需要回收產生的性能問題。
但要注意,這只是決定回收的時機,而不是回收的內容。這個延遲執行內存回收也就是個表面的現象,不管什么時候執行GC,能夠回收的內存最終都能回收,不能回收的肯定不能回收。唯一的影響是,因為回收是延遲執行的,你在查看內存的時候不能直觀地看到因為一個對象被廢棄而回收內存的過程,會產生迷惑。
但這對于解決內存泄露是無關緊要的。
內存泄露指的就是當你銷毀了一個對象的時候,它占用的內存卻無法被回收,這會導致可用內存越來越小最終溢出,在內存緊張的環境中將會造成系統崩潰。其原因多種多樣,但一般都是開發者的疏忽所致,沒有提供給系統足夠的可以銷毀對象的依據。
執行GC雖然和內存泄露沒有關系,但是如果不在測試前執行GC,你將看不到當時實際的不可回收內存的量,而內存泄露就是指不可回收內存的數量的增加。因此,測試內存回收將離不開GC方法。沒有使用GC方法的測試用例是沒有意義的,因為這其中摻雜了偶然性(什么時候執行GC)。不少荒謬的測試結果都是因為沒有在正確的位置執行GC導致的。
Flash Player雖然沒有開放發布狀態的手動gc,但調試版本是可以使用的,正好可以讓我們測試。此外下面的HACK代碼也可以在發布階段觸發GC。
try {new LocalConnection ().connect ( "gc" );new LocalConnection ().connect ( "gc" ); } catch ( e:Error ) {}但我再次強調,調用GC僅僅是用于測試。實際產品中調用GC基本沒有意義(除了用于控制GC時機),總之如果你的程序出現了內存泄露,那一定和GC沒有關系,請不要再在這種地方浪費寶貴的時間與精力。
只有在申請內存時才會觸發自動GC
AVM2的GC是在每次申請內存時,根據當前內存占用來觸發的。申請內存是一個必要因素。所以,如果你一直不進行申請內存的操作,就算內存達到了一個高值,它也不會進行GC。
這確實是個不合理的地方。但是,在實際環境中,一直不請求內存的情況是很少見的,就算出現,當時也未必處于內存的高值。這種情況主要出現在測試環境中,導致一些人會懷疑自動GC的功能是否正常。實際上這也是沒有必要的。
Flash中垃圾回收的條件
在AVM2中,除去特殊的BitmapData必須調用dispose才能回收內存外,其他的部分都是用引用計數法和標記清除法作為判斷是否應該回收內存的手段,而且并沒有提供主動回收的API,詳細部分請看這篇日志,我就不重復了。
http://www.cnblogs.com/cos2004/archive/2010/11/07/1870980.html
因此,你要回收一個對象,只要保證沒有任何對象引用它,而且他的方法沒有被當做事件函數——或者說,他和程序的其他部分已經沒有任何聯系,它就滿足了引用計數法的標準,就一定會被回收。做到這一點的方法就是一般說的“執行removeChild,removeEventListener,將對他的引用設置為null”。
但是,實際上回收一個對象的要求并沒有那樣嚴格,就在于FP除了引用計數法,還包括標記清除法。標記清除法是從程序的根對象開始(stage,靜態屬性,活動的定時器和加載器,ExternalInface.callBack)一級一級遍歷對象,只要遍歷不到,即使不滿足引用計數法的條件也可以回收。比如兩個對象互相引用,但是和外界都沒有關系,形成了孤島,它們就可以被回收,盡管它們因為互相引用使得引用數不為0。比起單純的引用計數,這種辦法能確實能找到已經無法再訪問到的實際上的閑置對象。所以,可以看到很多人的代碼實際上并沒有設置null,甚至沒有removeEventListener,它一樣可以被正常回收,少寫這些代碼可以使得程序更簡潔,要全部符合標記清除法的條件,會很累。
“無法被根訪問”,這種說法很曖昧,基本不能當做判斷依據。所以我下面會舉幾個具體例子,來說明什么樣的情況是符合標記清除法的要求的。
首先明確一點,標記清除法是只以能否能被根訪問作為唯一依據的,并不需要關注被引用的次數,請不要混淆。
- 屬性的相互引用是很明確的,一般都是一個對象包含著若干屬性,那么這個對象自然可以維持它的屬性的引用。如果這個類不會被回收(能夠被根訪問),他的所有屬性也都不會被回收。同樣的,如果這個類可以被回收的話(不能被根訪問),也就不會妨礙屬性的回收。所以你并不需要將所有屬性設置為null,除非你希望在對象存在時候就回收其屬性的內存,這種需求基本不存在。
- 靜態屬性是一個特殊的情況。靜態屬性本身就是根,所以你必須將其設置null才有可能被回收,沒有別的辦法。
- 至于在顯示列表中的對象。既然根(stage)可以用getChildAt訪問到自己的所有子對象,那么只要你在顯示列表中,就肯定不會被回收。然而,如果顯示對象的父層對象已經不再顯示列表內,它的子對象就算還在父層對象之中也沒有關系,因為它已經不能被stage訪問到了。所以你不需要removeChild各層的全部對象,而只需要removeChild最高一層的父對象即可。
- A.addEventListener(“event”,B.handler),像這樣添加過事件后,你可以認為B.handler成為了A的一個屬性(因為A在需要的時候要能調用B.handler),這里也符合屬性相互引用的原則。但是事件判斷起來的確要比屬性麻煩,因為相互引用的情況很多。在這里可以分為三種情況:
- 對自己監聽自己的事件,這相當于用自己的屬性保存自己引用,任何情況都不會阻礙自己被回收。
- 對自己的子對象(屬性或者child)監聽自己的事件。因為子對象本來就是自己在維持它的引用,那么即使它們會維持你的引用,也只會形成一個循環。一旦你和stage脫離了聯系,子對象同樣也會脫離聯系,當然也無法妨礙你自己被回收了。除非子對象因為一些原因可以單獨維持引用(諸如被保存在靜態屬性中),但這種情況很少見。
- 對自己的父對象(parent或者stage)監聽自己的事件。因為這使得你成為了父對象的一個屬性,只要parent或者stage不被回收,那么自己就不會被回收。尤其是stage,它肯定不會被回收。這種情況一般都會導致自己無法回收,是必須removeEventListener的。
總得來說,就是務必注意對stage,parent的事件監聽,其他情況一般都是不會妨礙回收的。而對stage,parent的監聽大多都是各種鼠標,鍵盤事件。數量并不多,專門注意這里可以杜絕大部分因為事件造成的內存泄露。
其實,內存泄露并不容易出現。按照普通的編程習慣,只有監聽stage事件這種做法會造成意料之外的泄露,一般都是可以順利回收的。這比每次都要手工回收內存要方便多了。
這里只有BitmapData是例外。除了遵從上面的規則外,要回收它的內存,必須手動調用dispose方法,習慣自動回收的人會很累。務必注意,Bitmap對象的bitmapData屬性是需要手動銷毀的,Loader加載的位圖是需要手動銷毀的,當你用一個生成的位圖作為位圖填充繪制平鋪的圖像后,在銷毀這個圖像后也必須銷毀這個位圖(所以你必須一直保存位圖的引用)。BitmapData是32位的未經任何壓縮的圖像,隨便一個體積都會非常大,不處理好它們的回收,一個BitmapData泄露就可以頂你數萬個復雜對象的泄露。
如果出現非常明顯的內存泄露,大部分時候都是位圖泄露。所以在研究上面的引用計數法和標記清除法以及GC之前,請先保證位圖部分不出問題。
弱引用時的例外
弱引用會改變垃圾回收的規則。如果使用了弱引用,addEventListener將不會影響對象回收,即使對stage添加監聽,也不會導致自己被回收。但是這同時也是缺點,因為有的時候你就是希望用引用限制住對象的回收,使用弱引用會使得這個對象有時回收有時不回收。雖然極少出現,但一旦出現,這種不容易重現的錯誤是很難查出來的。因此我并不推薦使用弱引用。
弱引用在AVM2中只有兩處:
- 一處是addEventListener的第5個屬性,名為userWeakReference,設置為true,監聽事件將不會影響對象回收。
- 一處是Dictionary的構造函數參數,名為weakKeys,設置為true,當鍵為復雜對象時,即使Dictionary存在,鍵依然可以被回收。注意,這里說的是鍵,不是值,值是不享受弱引用待遇的。這個屬性也寫得也很明白,是weakKeys。
內存泄露的查找方法
Flash Builder提供了一個概要分析工具,可以幫助我們查找內存泄露。大多數情況都可以幫助我們解決問題。可以查看下面的文章:
http://blog.csdn.net/bbmjfpig/archive/2010/12/30/6107347.aspx
關鍵點在于,檢測內存泄漏應該是“創建,取樣,銷毀,再創建,取樣”,然后以兩次取樣的對比數據來觀察泄露。因為對象在第一次創建時會有一些緩存數據,它們在設計上就不會隨著對象銷毀而回收的,比如類定義的緩存,比如皮膚。它們只會創建一次,和我們看到的泄露并不是一回事。
必要時可以執行強制GC
因為每次GC都需要消耗性能,對象越多,GC越慢。我理解Flash Player禁用發布版本的System.gc()是為了避免開發者濫用這個方法,但有些時候我們的確需要手動控制GC時機,因為GC過程如果遇到大量可回收對象會讓Flash Player卡住。
比如,我們需要在切換屏幕時回收一次內存,這時候卡是看不出來的,而不是切換完后播放動畫時回收然后讓動畫頓住。或者,我們會定期在必要的時候執行一次GC,將GC需要的時間分擔開。所以這時候用HACK方法強制執行一次GC也不失為一個選擇。當然,這和內存泄露半點關系都沒有。
Flash Player這個地方的設計特別的不好。它自己又不支持分步GC,一旦GC的時候沒有辦法避免卡的問題。結果GC的時機還不給控制……
微量剩余內存
測試中FLASH的確存在微量內存無限增加的問題,原因未知。我將50萬個對象扔在一個數組中,銷毀后確實會多出1M的內存占用(如果沒扔在數組中不會),但這個數量很小,但達到能看得出來的100M內存需要5000萬個對象,這個數額在通常情況下很難達到。
不過也有人說這只是對象銷毀而內存并未全部釋放的表現,實際上最后還是能完全釋放的。或者是因為totalMemory的不精確所造成的。這個我就不清楚了。
不過就算這個的確是FlashPlayer的BUG,也無傷大雅吧。
轉載于:https://www.cnblogs.com/axyz/archive/2011/09/16/2178978.html
總結
以上是生活随笔為你收集整理的Flash务实主义(五)——AS3的垃圾回收(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Dave Python 练习十五 --
- 下一篇: Tarjan的求双连通分量算法