Skip to content

🔰 Flash Player的垃圾内存回收机制:能否强制回收?

🕒 Published at:

Flash Player的垃圾内存回收机制:能否强制回收?

在Adobe Flash Player 10中,AS3的内部垃圾回收机制是怎样的?程序员可以强制FP回收内存吗?答案是否定的,但有方法。

1. 毛主席请猫吃辣椒的故事

建国初期,国家正在对资本主义工商业进行社会主义改造,毛泽东问周恩来、刘少奇等,如何才能让猫吃辣椒?刘少奇说,把猫逮住,用筷子捅下去。周总理说,先把猫饿上三天,然后把辣椒裹在肉里给它吃。毛主席都不赞成这两种做法,毛主席说,把辣椒抹在猫屁股上,猫觉得辣自然就去会舔,而且还会因为这样做而兴奋不已。

2. 强制垃圾内存回收的代码

AS3程序员没有办法强制Flash Player进行垃圾回收,但有一种类似于毛主席请猫吃辣椒的方法可以使用。

MoonSpirit在天地会撰写过一篇关于强制垃圾回收的文章:[as hack技术]垃圾回收机强制执行。文中例证,使用LocalConnection连接非法地址,并捕获导常,可以强制进行垃圾回收。作者在MoonSpirit的基础上,简单修改了一下代码,如下:

js
package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.net.LocalConnection;

	/**
	 * @author LIYI https://yishulun.com/
	 * 修改自MoonSpirit的强制垃圾回收测试代码
	 */
	public class GarbageCollectionTest extends Sprite {
		private const SQR_AMOUNT : int = 10000; // 方块数量        
		private var _container_sp : Sprite;// 容器sprite
		private var _sqrList: Array;// 所有方块的引用

		// 强制垃圾回收使用的对象
		private var conn1 :LocalConnection;
		private var conn2 :LocalConnection;

		public function GarbageCollectionTest() {
			init( );
		}

		private function init( ) : void{
			_container_sp = new Sprite( );
			addChild(_container_sp);
			//initNoBitmapDataView( );// 峰值4111K,手动回收两次变为9K
			initBitmapDataView( );// 峰值14K,最小为14K,手动回收基本无变化
		}

		// 初始化 通过通常手段 显示
		private function initNoBitmapDataView( ) : void {
			layoutTenThousandSqr( );

			unLayoutTenThousandSqr( );
			_sqrList = null;
			removeChild(_container_sp);
			_container_sp = null;
		}

		// 初始化 通过BitmapData快照 显示
		private function initBitmapDataView( ) : void {
			layoutTenThousandSqr( );

			unLayoutTenThousandSqr( );
			_sqrList = null;
			removeChild(_container_sp);
			_container_sp = null;

			doClearance( );
		}

		private function layoutTenThousandSqr( ) : void {
			_sqrList = new Array( );
			for(var i : int = 0; i < SQR_AMOUNT; i++){
				_sqrList.push(new Sprite());
				_sqrList[i].graphics.beginFill(0xff0000);
				_sqrList[i].graphics.drawRect(0,0,100,100);
				_sqrList[i].graphics.endFill();
				_container_sp.addChild(_sqrList[i]);
			}
		}

		// 不显示
		private function unLayoutTenThousandSqr( ) : void {
			for(var i : int = 0; i < SQR_AMOUNT; i++){
				_container_sp.removeChild(_sqrList[i]);
				delete _sqrList[i];
			}
		}

		// 精髓,垃圾回收机强制调用
		private function doClearance( ) : void {
			trace("clear");
			try{
				conn1 = new LocalConnection();
				conn1.connect("liyi garbage collection 1");
				conn2 = new LocalConnection();
				conn2.connect("liyi garbage collection 1");
			}catch(e :*){}
			finally{
				conn1 = null;
				conn2 = null;
			}
		}
	}
}
package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.net.LocalConnection;

	/**
	 * @author LIYI https://yishulun.com/
	 * 修改自MoonSpirit的强制垃圾回收测试代码
	 */
	public class GarbageCollectionTest extends Sprite {
		private const SQR_AMOUNT : int = 10000; // 方块数量        
		private var _container_sp : Sprite;// 容器sprite
		private var _sqrList: Array;// 所有方块的引用

		// 强制垃圾回收使用的对象
		private var conn1 :LocalConnection;
		private var conn2 :LocalConnection;

		public function GarbageCollectionTest() {
			init( );
		}

		private function init( ) : void{
			_container_sp = new Sprite( );
			addChild(_container_sp);
			//initNoBitmapDataView( );// 峰值4111K,手动回收两次变为9K
			initBitmapDataView( );// 峰值14K,最小为14K,手动回收基本无变化
		}

		// 初始化 通过通常手段 显示
		private function initNoBitmapDataView( ) : void {
			layoutTenThousandSqr( );

			unLayoutTenThousandSqr( );
			_sqrList = null;
			removeChild(_container_sp);
			_container_sp = null;
		}

		// 初始化 通过BitmapData快照 显示
		private function initBitmapDataView( ) : void {
			layoutTenThousandSqr( );

			unLayoutTenThousandSqr( );
			_sqrList = null;
			removeChild(_container_sp);
			_container_sp = null;

			doClearance( );
		}

		private function layoutTenThousandSqr( ) : void {
			_sqrList = new Array( );
			for(var i : int = 0; i < SQR_AMOUNT; i++){
				_sqrList.push(new Sprite());
				_sqrList[i].graphics.beginFill(0xff0000);
				_sqrList[i].graphics.drawRect(0,0,100,100);
				_sqrList[i].graphics.endFill();
				_container_sp.addChild(_sqrList[i]);
			}
		}

		// 不显示
		private function unLayoutTenThousandSqr( ) : void {
			for(var i : int = 0; i < SQR_AMOUNT; i++){
				_container_sp.removeChild(_sqrList[i]);
				delete _sqrList[i];
			}
		}

		// 精髓,垃圾回收机强制调用
		private function doClearance( ) : void {
			trace("clear");
			try{
				conn1 = new LocalConnection();
				conn1.connect("liyi garbage collection 1");
				conn2 = new LocalConnection();
				conn2.connect("liyi garbage collection 1");
			}catch(e :*){}
			finally{
				conn1 = null;
				conn2 = null;
			}
		}
	}
}

然后呢,作者调用doClearance方法,这次无论是否手动回收,内存占用无变化,如下图所示,这说明doClearance方法已经强制了垃圾内存回收,它作用了!

[此处有图,但被岁月冲走了]

那么,为什么创建两次没用的LocalConnection可以强制回收内存呢?

3. AS3垃圾内存回收机制

在Flash Player初始化运行时,会向操作系统申请一大块内存,如果程序很小,有可能根本用不了这么多内存,但FP在开始时不考虑这些,大多数情况下,第一次申请的内存总是不够用的。第一次申请的内存大小,与操作系统、浏览器环境有关。

当Flash Player发现已经申请的内存不够用时,它会再向操作系统申请一大块内存。但在申请之前,请注意,FP会尝试进行垃圾内存回收。那么它是如何回收的呢?

Flash Player在内部使用懒惰式引用计数回收方案进行垃圾内存回收,懒惰式指:FP并不会一次把所有可以回收的对象全部回收,它一次仅会回收一部分,如果内存不够用,它会向操作系统申请,如果系统无内存了,它会再次回收,如果全部回收了仍不够用,Game Over!引用计数指:FP在内部给每个对象标记一个记号,当没有任何对象引用此对象时,它即是可以被回收的;如果一个容器内有许多相互关联的对象,当把这个容器从显示列表中移除,并且置为null后,它也是可以被回收的。

在小节2中,作者问到为什么要手动单击两次“运行垃圾回收器”按钮,这是由于FP垃圾回收机制的懒惰性造成的。

在清楚了FP的内部垃圾回收机制之后,我们便可以回答,为什么创建两次没用的LocalConnection,并且连接并不存在的地址,故意抛出异常然后捕获,可以强制垃圾回收呢?因为,在AS3中LocalConnection是比较占用内存的对象,两次创建该类对象并尝试进行连接的内存开销大小足以请Flash Player重新向操作系统申请内存,而在申请之前,FP会尝试回收。原理即是这么简单,非独使用LocalConnection可以,其它较耗内存的对象也可以。

2008年5月