淺談Flash的垃圾回收機制

Flash Player的garbage collection(GC)分兩種運行方式,一種是“引用計數法”(Reference Counting),一種是“標記-清除法”(Mark Sweeping)。

引用計數法是通過計算指向某個對象的引用的數量來確定是否清除該對象。如果一個對象的引用數量爲0,表示程序無法再訪問到該對象,則清除該對象;如果引用計數不爲0,則不清除。這種方法運行代價較小,但是這種方法無法清除存在循環引用關係的對象集合。標記-清除法是從程序的根對象開始,遍歷每個引用指向的對象。遍歷經過的對象,則將其標記。最後清除所有沒有打上標記的對象。這種方法比較徹底,但是運行代價較高。

FlashPlayer運行GC的時間並不固定,它會根據你的內存的佔用情況來決定運行GC的時間。它會根據用戶機器的內存值來設定一個閥值,然後將程序的佔用內存量保存在該閥值左右。

正因爲FlashPlayer這種“不確定”的GC機制,所以我們所要做的主要工作是確保創建的對象在不需要的時候可以被釋放。確保對象可以被釋放的大原則是沒有外部引用指向該對象,除了一般情況下的沒有將外部引用顯示地設爲null之外,以下的情況也會導致對象無法釋放:

  1. 沒有remove監聽的事件。比如,A對象對某個事件進行監聽,監聽函數(Event Handler)存在於B對象中,則相當於A對象會保存一個B對象的方法的引用,會導致B對象的內存無法釋放。

解決方法:注意remove掉監聽事件;或者在調用addEventListener()時,將監聽函數設爲弱引用,但這種做法只適合一次性的監聽。

使用BindingUtils.bindSetter()、ChangeWatcher.watch()綁定某個對象之後,沒有清除該綁定。道理同1,其實綁定某個對象,也就是監聽其發出的PropertyChange事件。

解決方法:使用ChangeWatcher.unwatch()來清除綁定關係。

聲明瞭樣式,並在樣式中使用了嵌入式資源。比如在標籤中定義了樣式名稱。一個對象定義了樣式,相當於對外聲明瞭一個全局可用的樣式,因此會到導致外部保存了該對象的引用,可能導致對象無法釋放。

解決方法:解決方法很多,可以使用動態加載的樣式,或者使用一個類或模塊(Module)專門管理樣式,這些解決方法取決程序的架構設計。

使用ExternalInface.callBack()聲明瞭對外的API函數。類似於情況1,一個對象對外聲明瞭API,就使外部保存了指向該對象的引用。

解決方法:如果之前使用了ExternalInface.callBack("APIName", functionName)聲明瞭一個API,則可以使用ExternalInface.callBack("APIName", null)取消該API。

某些控件(類似TextInput),或者由這類控件構成的自定義組件,當焦點在這些控件上時,即使從DisplayList移除掉這些控件並刪除引用,這些控件對象也無法釋放。這個問題還有人提出來是一個Bug(http://bugs.adobe.com/jira/browse/SDK-14781)。這個問題估計和flash的焦點管理機制有關。

解決方法:目前的解決方法只能是等焦點重新轉移到其他控件上(比如點擊了其他控件),如此之前的控件對象就可以被GC釋放。

那應該在什麼時候做好垃圾清理的準備工作呢?之前有的文章說應該監聽組件的removeFromStage事件,在其處理方法中進行垃圾清理的準備工作(清除引用,刪除監聽器,清除綁定關係,取消對外API等工作)。

其實這種方法不太確切。因爲removedFromStage事件是當組件從DisplayList上移除的時候發出的,並不代表該組件對象的生命週期已經終結。只要程序保留了該組件對象的引用,可以再重新把該組件對象添加到DisplayList上(此時,該組件對象會發出addedToStage事件)。如果單純在removedFromStage事件的監聽函數中做該對象的垃圾清理準備工作,當組件重新被使用的時候,可能導致該組件對象原來的狀態被破壞而無法使用。

因此,比較好的實踐方法應該是,利用addedToStage、removedFromStage兩個事件的對應關係,在removedFromStage事件的處理方法中執行垃圾清理的準備工作(清除引用,刪除監聽器,清除綁定關係,取消對外API等作),而在addedToStage事件的處理方法中執行removedFromStage事件的處理方法的反操作(設置引用、添加監聽、設定綁定關係、設置API...也可以認爲是一個組件對象的初始化操作),這樣就可以保證一個組件對象被從DisplayList上移除後,可以釋放相應內存;如果保存其引用,並將其重新添加到DisplayList上,又可重新使用。

最後翻譯一段關於內存清理的建議:

  1. usage of instance members instead of static members can easily be detected with the profiler (replace by static members where possible)

使用實例成員(instance members),而不是用靜態成員(static members),可以更容易地被profiler檢查到。因此,儘可能地使用實例成員,而不要用靜態成員。

usage of weak references and / or removal of eventListeners after consumption of the event (if posible) helps reducing the memory usage

在事件完成之後,將其設爲引用 而且/或者(and / or) 將其remove掉,有助於減少內存使用。

moduleLoader.unloadModule leaks memory, use moduleLoader.url=null instead

moduleLoader.unloadModule()會導致內存泄露,因此建議使用將moduleLoader.url=null.

module memory is freed at arbitrary times (not at unload)

module內存的釋放時間是不確定(並不是在unload的時候)。

runnning debug version of modules leaks huge amounts of memory no matter which container is used

使用debug版本的module會導致大量的內存泄露,不管其容器是否使用。

declaring modules as modules in the configuration of a flex builder 3 project (and not as applications like in FlexBuilder 2) and optimizing for a specific application reduces module size drastically

將一個程序塊聲明爲module,而不要將其聲明爲application,並且設置各module專門爲一個application進行優化,能大量節約內存。

forcing garbageCollection (double LocalConnection.connect hack) is necessary in order to measure leaks and to keep memory under control

在適當的時候,爲了內存可控,可強制使用垃圾收集器(garbageCollection),方法如下:

1try {
2    import flash.net.LocalConnection;
3    var conn1:LocalConnection = new LocalConnection ();
4    var conn2:LocalConnection = new LocalConnection ();
5    conn1.connect("gc");
6    conn2.connect("gc");
7}catch(e:Error){}
use the release version of the module swf

使用release版的module swf。

uninstall the debug flash player ("uninstall_flash_player.exe")

卸載debug版的flash player。

install the release version of the flash player ("install_flash_player_active_x.msi")

安裝release版的flash player。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章