AS3中Timer,setTimeout,setInterval與垃圾回收處理

Timer

1. 官方說法

  無限次Timer可能會引起內存泄露,有限次Timer不會引起內存泄露

2. 實際情況(見測試用例)

  a)無限次Timer

    i.Timer在運行中且註冊了TimerEvent.Timer事件偵聽器,則偵聽器對象本身不會被回收

    ii.Timer沒有調用start()或者start後調用了stop(),偵聽器對象會被垃圾回收

    iii.Timer調用了start(),但是沒有對Timer註冊事件偵聽器或者事件偵聽器被移除,不存在對偵聽器垃圾回收問題

  b)有限次Timer

    i.Timer在運行中且對Timer註冊了事件偵聽器,則偵聽器對象不會被回收

    ii.Timer調用了stop(),偵聽器對象會被回收

    iii.Timer在運行了指定的repeat次數後,偵聽器對象會被回收

3.結論

  Timer在運行中且註冊了事件偵聽器(一般用Timer,肯定會註冊事件偵聽器),偵聽器對象不會被回收;

  由於有限次Timer遲早有結束的那一刻,所以有限次Timer一般不會造成內存泄露,但是可能會造成偵聽器對象延遲釋放;

  而無限次Timer則可能引起內存泄露,在一個對象釋放後,如果對象內部的Timer沒有停止或者沒有移除事件偵聽器,則對象永遠不會被釋放

4.好的習慣

  a)能用有限次Timer的情況,不要用無限次Timer

  b)無論是有限次Timer還是無限次Timer,在不再需要Timer時顯式地調用Timer.stop(),或者移動事件偵聽器(removeEventListener),並且建議兩項工作都做。

5.測試用例及說明

  測試一中,在文檔類中創建了一個TestTimer(test),注意這是一個局部變量。在TestTimer構造函數中,創建了一個每100毫秒觸發一次的無限次Timer,並添加相應的偵聽器,最後調用Timer.start()啓動Timer,這時雖然程序中沒有任何對TestTimer的引用,運行GC可以發現TestTimer對象不會被銷燬;

  在測試二中,與測試一唯一不同的地方在於沒有對timer添加事件偵聽器,即少了timer.addEventListener(TimerEvent.TIMER, handleTimer),在profile中運行gc,發現testTimer的實例由1變爲0,說明這種情況不會造成內存泄露;

  在測試三中,與測試二的不同在於,添加了事件偵聽器,但是沒有調用timer.start(),這時發現結果與測試二相同;針對測試三,可以測試另一種情況,在構造函數中調用timer.start(),但是在handleTimer中調用timer.stop()或者removeEventListner(TimerEvent.TIMER, handleTimer),測試結果與測試二相同;

  在測試四中,指定了一個執行200次的Timer(new Timer(100,200)),在Timer執行結束前,無法回收TestTimer實例,在執行完畢後可以正常回收,說明偵聽器是否能被gc,是完全依賴於Timer是否在運行中,對於無限次Timer,如果不調用stop(),偵聽器則不能被回收;



       public function Test(){
           var test:TestTimer = new TestTimer();
       }

}

public class TestTimer {

       private var timer:Timer;

       public function TestTimer() {
           timer = new Timer(100);
           timer.addEventListener(TimerEvent.TIMER, handleTimer);
           timer.start();
       }

       private function handleTimer (event:TimerEvent):void {
           trace(“timer”);
       }

}

setInterval

     如果不調用clearIntervalsetInterval調用的函數所在對象永遠不會被回收,這是顯而易見的,有懷疑精神的童鞋可以自己去做測試,就不寫測試代碼了;如果懷疑這個的話,建議也去測試一下,as(1+1)==2是不是返回trueo(_)o~~~哈哈!!

setTimeout

  1, 如果不調用clearTimeout,setTimeout引用的對象永遠不會被回收,無論timeoutHandler是否已經執行,clearTimeout的作用不僅僅是中斷未執行的timeoutHandler,更重要的是,如果不調用clearTimeout,聲明timeoutHandler的對象不會被gc

  2, 測試如下代碼,發現gc時,TestTimeOut的實例能夠正常釋放;

  3, 如果在下面的代碼塊的timeoutHandler中不調用clearTimeout(timeoutId),雖然此時timeoutHandler已經執行過,TestTimeOut對象在gc執行時仍然不會被回收;

  4, 關於setTimeout,多說一句, setTimeout其功能相當於只執行一次的Timer,而Timer執行一次後,無論是否stop()或者removeEventListener,其事件偵聽對象都可以被正常gc;個人覺得這應該也算是設計上的失敗,我一直以爲clearTimeout只是用來在timeoutHandler執行之前用來取消setTimeout的;

  而且其中文幫助文檔是這樣說的(如果是翻譯出錯,算我倒黴):

  如果打算使用 clearTimeout() 方法取消 setTimeout() 調用,請確保將 setTimeout() 調用分配給一個變量(clearTimeout() 函數稍後將引用該變量)。如果不通過調用 clearTimeout() 函數取消 setTimeout() 調用,則不會將包含設置的超時 closure 函數的對象作爲垃圾回收。

  如果只看前半句,我絕對不會想到這個還會影響到gc,小小地鑽一下文字牛角尖:如果我不打算取消本次setTimeout()調用,那麼是不是也就不必要記住tiemeoutId,那麼也就無從調用clearTimeout了;

  後半句,個人認爲是有歧義的(當然還是可能是翻譯出了差錯),而且我想了很久,這段話連起來讀,第二種理解也符合一些:

  理解一:如果不調用clearTimeout,那麼包含超時closure函數的對象永遠也無法垃圾回收(無論超時回調closure函數是否已經執行),這個和測試結論一致,也是正確的理解;

  理解二:如果不調用clearTimeout函數,在設置的超時closure函數在執行前,包含該函數的對象不能作爲垃圾回收;

  假設設置的超時closure函數所在對象沒有其他引用,或者closure只是在主函數內部定義的一個局部函數, setTimeout調用執行前的確是不應該回收的,這樣第二種理解也可以自圓其說。



       public function Test(){
           var test:TestTimeOut = new TestTimeOut ();
       }

}

public class TestTimeOut {

       private var timeoutId:int;

       public function TestTimeOut () {
           timeoutId = setTimeout(timeOutHandler);
       }

       private function timeOutHandler ():void {
           clearTimeout(timeoutId);
       }

}

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