java和flash的垃圾回收都是一個比較熱門的話題,今天我也用一個例子來測試下flash的強制垃圾回收。主要用到的而一個類是LocalConnection。
在Flash player的debug版本中提供了gc的方法
Flash Player初始化運行時,會向操作系統申請一大塊內存,如果程序很小,有可能根本用不了這麼多內存,但FP在開始時不考慮這些,大多數情況下,第一次申請的內存總是不夠用的。第一次申請的內存大小,與操作系統、瀏覽器環境有關。
當Flash Player發現已經申請的內存不夠用時,它會再向操作系統申請一大塊內存。但在申請之前,請注意,FP會嘗試進行垃圾內存回收。那麼它是如何回收的呢?
Flash Player在內部使用懶惰式引用計數回收方案進行垃圾內存回收。
懶惰式指:FP並不會一次把所有可以回收的對象全部回收,它一次僅會回收一部分,如果內存不夠用,它會向操作系統申請,如果系統無內存了,它會再次回收,如果全部回收了仍不夠用,Game Over!
引用計數指:FP在內部給每個對象標記一個記號,當沒有任何對象引用此對象時,它即是可以被回收的;如果一個容器內有許多相互關聯的對象,當把這個容器從顯示列表中移除,並且置爲null後,它也是可以被回收的。
在清楚了FP的內部垃圾回收機制之後,我們可以創建兩次沒用的LocalConnection,並且連接並不存在的地址,故意拋出異常然後捕獲,就強制垃圾回收,因爲,在AS3中LocalConnection是比較佔用內存的對象,兩次創建該類對象並嘗試進行連接的內存開銷大小足以請Flash Player重新向操作系統申請內存,而在申請之前,FP會嘗試回收。原理即是這麼簡單,非獨使用LocalConnection可以,其它較耗內存的對象也可以。
下面是我寫的測試代碼,主要測試三中情況下內存的使用情況。
package
{
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.net.LocalConnection;
public class MemeryGcTest extends Sprite
{
private const num:int = 30000; //子元素個數 根據自己的電腦配置來設置
private var parentContainer:Sprite;//父容器
private var childrenRect:Array;//所有子元素的引用
public function MemeryGcTest(){
init();
}
private function init() : void{
parentContainer=new Sprite();
addChild(parentContainer);
createAllChildrens();
removeAllchildrens();
setChildrenNull();
//doGc();
}
/**
* 移除所有對象
*
*/
private function removeAllchildrens():void {
removeAllChildrens( );
removeChild(parentContainer);
}
/**
* 設置不用對象爲null 否則不會進行垃圾回收
*
*/
private function setChildrenNull():void{
childrenRect = null;
parentContainer = null;
}
/**
* 創建所有子元素
*
*/
private function createAllChildrens() : void {
childrenRect=new Array();
for(var i:int = 0;i<num; i++){
var sprite:Sprite=new Sprite();
childrenRect.push(sprite);
sprite.graphics.beginFill(0xff0000);
sprite.graphics.drawRect(0+i/50,0,100,100);
sprite.graphics.endFill();
parentContainer.addChild(sprite);
}
}
/**
* 移除所有子元素
*
*/
private function removeAllChildrens():void{
for(var i:int=0;i<num;i++){
parentContainer.removeChild(childrenRect[i]);
delete childrenRect[i];
}
}
/**
* 強制垃圾回收
*
*/
private function doGc():void{
try{
var conn1:LocalConnection= new LocalConnection();
conn1.connect("testGc");
var conn2:LocalConnection= new LocalConnection();
conn2.connect("testGc");
}catch(error:Error){
conn1 = null;
conn2 = null;
}
}
}
}
第一種情況,不設置不用元素爲null和強制垃圾回收
private function init() : void{
parentContainer=new Sprite();
addChild(parentContainer);
createAllChildrens();
removeAllchildrens();
//setChildrenNull();
//doGc();
測試結果如下:
可以看出佔用的內存比較高,點擊GC按鈕內存依然是“居高不下”。
第二種情況,設置不用元素爲null但不強制執行垃圾回收。
private function init() : void{
parentContainer=new Sprite();
addChild(parentContainer);
createAllChildrens();
removeAllchildrens();
setChildrenNull();
//doGc();
測試結果如下:
設置null後雖然剛開始峯值很高,但是Flash Player執行了垃圾回收,很快內存下降到12K。
第三種情況,設置不用對象爲null並強制進行垃圾回收。
private function init() : void{
parentContainer=new Sprite();
addChild(parentContainer);
createAllChildrens();
removeAllchildrens();
setChildrenNull();
doGc();
測試結果如下:
可以看出強制垃圾回收確實執行了。
內存泄露舉例:
1、引用泄露:對子對象的引用,外部對本對象或子對象的引用都需要置null。
2、系統類泄露:使用了系統類而忘記做刪除操作了,如 BindingUtils.bindSetter(),ChangeWatcher.watch()函數時候完畢後需要調用 ChangeWatcher.unwatch()函數來清除引用 ,否則使用此函數的對象將不會被刪除; 類似的還有MUSIC,VIDEO,IMAGE,TIMER,EVENT,BINDING等。
3、效果泄露:當對組件應用效果Effect的時候,當本對象本刪除時需要把本對象和子對象上的Effect動畫停止掉,然後把Effect的target對象置null; 如果不停止掉動畫直接把 Effect置null將不能正常移除對象。
4、SWF泄露:要完全刪除一個SWF要調用它的unload()方法並且把對象置null。
5、圖片泄露:當Image對象使用完畢後要把source置null。
6、聲音、視頻泄露: 當不需要一個音樂或視頻是需要停止音樂,刪除對象,引用置null。
內存泄露解決方法:
1. 在組件的REMOVED_FROM_STAGE事件回掉中做垃圾處理操作(移除所有對外引用(不管是VO還是組件的都需要刪除),刪除監聽器,調用系統類 的清除方法) 先remove再置null, 確保被remove或者removeAll後的對象在外部的引用全部釋放乾淨。
2. 利用Flex的性能優化工具Profile來對項目進程進行監控,可知道歷史創建過哪些對象,目前有哪些對象沒有被刪除,創建的數量,佔用的內存比例和用量,創建過程等信息。
總結:關鍵還是要做好清除工作,自己設置的引用自己要記得刪除,自己用過的系統類要記得做好回收處理工作。 以上問題解決的好的話不需要自定義強制回收器也有可能被系統正常的自動回收掉。