ActionScript垃圾回收

2024-02-13 06:32
文章标签 回收 垃圾 actionscript

本文主要是介绍ActionScript垃圾回收,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

谈谈ActionScript垃圾回收  

2011-08-23 来自 Kevin Cao's Blog 编辑 wangguo 有17472人浏览
Actionscript 垃圾回收

在《给AS程序员的一点建议一文》中我提到了释放资源的重要性。最近在一些项目过程中我又对这方面有了更多的理解,在此希望能够分享给大家。首先让我们来回顾一下关于垃圾回收(Garbage Collection,下文简称GC)的一些知识。要阅读本文,你需要对GC机制有些基本认识。

在ActionScript中,我们没有API可以直接删除一个对象,也不能控制Player进行GC。但是GC的行为是可以预估的,作为开发者,我们需要了解的是GC执行的时机是发生在需要向操作系统请求分配内存的时候。

 

 

 

 

从上面的模拟图我们可以看到:

  • Player以块的方式请求和释放内存。GC的结果不一定就是更少的内存占用,也有可能是从操作系统获得更多的可用内存。
  • Player会在某些GC过程中把内存中未使用部分组合成可以释放的块还给操作系统。
  • 此外还要注意的是Player为了避免占用太多的CPU资源,会将一些GC操作分到不同的时间片中运行,所以一次GC过程并不一定清理完所有可回收资源。

一次GC过程(GC Pass)分为以下两个步骤:

 

Reference Counting

 

统计所有对象的引用计数,如果某个对象没有任何引用,就标记为可回收。

 

 

 

这个操作很好理解,需要强调的是weak reference(弱引用)是不参与计算的。引用计数是一个相对省CPU的操作,能够筛选出大部分可回收资源,但是对一些循环引用的情况就无能为力了。在下图中,标记为绿色的对象每个的引用数都为1,但它们明显是应该被回收的。

 

 

所以GC需要进行第二个步骤:

 

Mark Sweeping

 

这个步骤是从根对象(Root)开始轮询对象的引用。所谓的根对象包括:

  • Stage对象
  • 静态变量
  • 局部变量

这种方式足够精确,能够成功筛选出上图中绿色标记的对象,而它的代价就是较大的计算开销。

为了帮助GC过程更高效的执行,最好是能在第一步引用计数中就把需要回收的对象都标记出来。具体的做法就是把所有不需要的对象引用全部清空,包括:

  • 删除成员变量的引用
  • 从可视对象列表上移除对象
  • 移除事件监听

难点:事件监听是否会造成对象不能回收?这个问题要具体分析,有些情况可以,有些情况却不可以。归根结底还是引用关系的问题。来看下面这个例子:

 

 

Actionscript代码
  1. Foo.as:  
  2.    
  3. public class Foo extends Sprite  
  4. {  
  5.     private var bar:Sprite;  
  6.     public function Foo()  
  7.     {  
  8.    
  9.         //监听bar发出的事件。可以看作是bar引用了foo,因为foo的引用被保存在bar的监听者数组里。  
  10.         bar.addEventListener(MouseEvent.CLICK, clickHandler);  
  11.         addChild(bar);  
  12.     }  
  13.    
  14.     private function clickHandler(event:MouseEvent):void  
  15.     {  
  16.         ...  
  17.     }  
  18. }  
 
Actionscript代码
  1. Main.as:  
  2.    
  3. // 创建foo实例  
  4. var foo:Foo = new Foo();  
  5. addChild(foo);  
  6.    
  7. // do something with foo...  
  8.    
  9. // 清除foo的引用  
  10. removeChild(foo);  
  11. foo = null;  
 

我们看到foo引用了bar,而bar又通过事件监听的联系引用了foo,这就构成了一个循环引用。根据前文对GC步骤的分析,这两个对象都必须到第二步mark and sweep才能被标记出来。

如果我们对事件监听用弱引用的方式:

 

Actionscript代码
  1. bar.addEventListener(MouseEvent.CLICK, clickHandler, false, 0, true);  
 

由于弱引用不计入引用计数,所以现在foo的引用数为0。GC在第一步操作中就能把foo标记出来,从而减少了一些运算开销。这也是为什么Grant Skinner呼吁把弱引用作为事件监听的默认方式的原因。

但是作为最佳实践,我们还是提倡要手动移除事件监听。以下代码添加一个destroy()方法(也有习惯命名为dispose()或kill()等)到Foo对象中:

 

Actionscript代码
  1. public function destroy():void  
  2. {  
  3.     bar.removeEventListener(MouseEvent.CLICK, clickHandler);  
  4.     removeChild(bar);  
  5.     bar = null;  
  6. }  
 

在清除foo的引用之前执行destroy()方法:

 

Actionscript代码
  1. foo.destroy();  
  2. removeChild(foo);  
  3. foo = null;  
 

经过我们如此处理,两个对象的引用数全都为0。GC只需第一步处理就完成任务,我们节约了更多的运算开销!

从上例可以看出在一般情况下,监听子对象的事件不会影响GC。不管是弱引用方式的监听,还是显式移除监听,都只是帮助GC更高效运行的手段,而不会 影响GC的结果。但是如果事件监听造成的是对象以外的引用关系,情况就不同了,并且很有可能造成回收失败。一个常见的错误例子是监听Stage对象的 RESIZE事件,如果没有显式移除这个监听或者是没有采用弱引用方式,那么这个对象就不会被GC回收的。所以我建议大家还是要尽可能的显式移除监听,切 断引用关系。

我们现在已经用对GC最友好的方式做好了清理准备,但是对象还没有从内存中删除。在等待GC执行的这段时间,对象内的代码还在继续执行。比如加在对 象上的ENTER_FRAME事件监听处理还在继续执行,对象内的MovieClip或Sound都还在继续播放。我们一定要避免这种情况的发生,所以在 切断引用之前,我们还要在destroy()方法中做些清理工作。我们要做的工作包括:

  • clearInterval(),clearTimeout()
  • timer.stop()
  • loader.unload()/loader.unloadAndStop()
  • movieclip.stop() 如果有子MC的,也要停止播放
  • bitmapData.dispose()
  • 关闭LocalConnection,NetConnection,NetStream
  • 停止音频和视频的播放
  • 删除Camera和Microphone对象的引用
  • 调用子对象的destroy()方法,如果有的话

其实这些都是在开发中管理资源的基本常识,归结为一句话就是:谁创建了对象,谁就要负责清理该对象。

下面就以一些我在实际项目中开发的destroy()方法为例,看代码说话:

 

Actionscript代码
  1. public function destroy():void  
  2. {  
  3.     // List是一个继承自Sprite的自定义子类  
  4.    
  5.     // 移除list的事件监听  
  6.     list.removeEventListener.remove(MouseEvent.CLICK, clickHandler);  
  7.    
  8.     // 我们创建了list实例,也要负责清理  
  9.     // 调用list对象自己的destroy()方法  
  10.     list.destroy();  
  11.    
  12.     // 将list从显示列表移除  
  13.     // 这一步并非必须的步骤,原因是list的父对象会被移除,这样list和stage就没有联系了。  
  14.     // 但是在我的代码中,list还有可能会被添加到外部的容器中(比如stage),那么这一步就是必须的了。  
  15.     list.parent.removeChild(list);  
  16.     list = null;  
  17.    
  18.     // --------------------  
  19.     // loader是一个来自tweenmax类库中的ImageLoader实例  
  20.    
  21.     // 如果loader已经创建  
  22.     if(loader)  
  23.     {  
  24.         // 调用loader的dispose()方法,优秀的第三方类库都应该有良好的资源管理机制。  
  25.         // 参数true表示把加载的内容从显示列表上移除,帮我们节约了代码。  
  26.         loader.dispose(true);  
  27.         loader = null;  
  28.     }  
  29.    
  30.     // --------------------  
  31.     // lightbox实例变量保存了一个外部引用  
  32.     // 根据谁创建谁清理的原则,我们在这里不需要负责该对象的清理,只要删除引用就可以了。  
  33.     lightbox = null;  
  34. }  
 

另一个示例的destroy()方法演示了对数组中对象的处理方法:

 

Actionscript代码
  1. public function destroy():void  
  2. {  
  3.     // cells是一个数组,包含了一组子对象  
  4.     var i : int, n : int;  
  5.     n = cells.length;  
  6.     for(i = 0; i < n; i++)  
  7.     {  
  8.         // 执行每一个子对象的destroy()方法  
  9.         cells[i].destroy();  
  10.         // 删除子对象的引用  
  11.         cells[i] = null;  
  12.     }  
  13.    
  14.     // 删除数组自身的引用  
  15.     cells = null;  
  16.    
  17.     // 如果不需要得到每一个子对象的引用,我们也可以简单的用以下代码来清理数组:  
  18.     //cells.length = 0;  
  19.     //cells = null;  
  20. }  
 

在结构更复杂的项目里,我们还可以抽象出一个IDestroyable接口,让需要执行清理的自定义对象实现这个接口。这样我们的清理代码可以写为:

 

Actionscript代码
  1. var n:int = this.numChildren;  
  2. for(var i:int = n-1; i >= 0; i--)  
  3. {  
  4.     if(this.getChildAt(i) is IDestroyable)  
  5.     {  
  6.         IDestroyable(this.getChildAt(i)).destroy();  
  7.         this.removeChildAt(i);  
  8.     }  
  9. }  
 

 

总结:GC好比是ActionScript城市的环卫工人,我们的每个类都是从事劳动生产的市民。优秀的市民会把生产垃圾分类安放到回收点,而不文 明的市民则把垃圾丢得到处都是。你说哪种做法让城市的清扫工作变得更加高效?所以请大家谨记“谁创建谁清理”的原则,做一位ActionScript好市 民。

 

 

  上文中我们介绍了GC的工作机制和帮助GC更好工作的最佳实践。其实只要我们遵守谁创建谁清理的原则来管理对象,就能基本上避免回收失败,也就是我们通常说的内存泄漏问题。但是在实际项目中我们还会看到各种原因引起的内存泄漏,接下来就让我们一起来找出病因。 

 

首先我们需要观察症状,也就是内存的使用曲线。排查的方法是反复执行一些创建和删除对象的方法、反复加载和卸载子文件。如果内存曲线一路飙升、或者是居高不下,都表明发生了内存泄漏问题。观察内存占用可以直接使用操作系统的资源管理器,也可以用Hi-ReS-Stats这个类。

 

第二个需要观察的地方,是Player输出的load和unload信息。加载和卸载外部文件,是内存泄漏问题的重灾区。在调试阶段,我一般会在主文件加一个执行System.gc()语句的按钮。一旦卸载了一个子文件,就手动触发若干次GC。如果没有输出子文件的卸载信息,那么就说明出现泄漏了。

 

 

第三个可以帮助我们排查问题的地方是Profiler工具,当你删除了对象引用,并手动触发GC以后,可以观察这个对象是否还存在内存中。Profiler可以说是排查内存泄漏问题的终极工具,唯一的问题就是会拖慢整体的运行速度,比较慢。

 

 

观察到问题现象以后我们得顺藤摸瓜,找出到底是那个对象占着内存不放,然后对症下药。下面我们就来分析几个内存泄漏的疑难杂症。

 

病例一:小心loaderContext和applicationDomain

 

ActionScript 3的Loader对象远没有我们想象中那么简单,内存泄漏问题有很大一部分是由于不当的加载和卸载操作引起的。我在研究Gaia框架的内存泄漏问题的时候发现了一处由于没有删除LoaderContext的引用而造成的卸载失败问题,其实就是没有释放应用程序域所造成的。应用程序域是一个需要被重视的对象,它对加载和卸载的影响有如下两点:

  • 1. 如果子SWF文件是加载到主应用程序域里的,那么这个文件是不能卸载的(前提是子SWF文件内的类定义没有被主应用程序域里定义所覆盖)。
  • 2. 如果子SWF文件是加载到子应用程序域内(Loader的默认方式),那么这个文件是一定能够被卸载的。

关于应用程序域的知识可以看我以前翻译的文章。 根据类定义在主应用程序域里的向下覆盖原则,我们还可以考虑以下情况:如果再次加载相同的子SWF文件到主应用程序域,子文件里所包含的类定义将全部忽 略,并不会注册到主应用程序域中。这次加载的SWF文件则是可以被卸载的。换句话说,一旦类定义被加入到主应用程序域里就不能够被删除。而没有加载到主应 用程序域内的对象如果不能卸载就肯定是内存泄漏。

实际开发中除了一些确实不需要卸载的模块代码需要加载到主应用程序域中,一般我们还是将对象加载到子应用程序域中去的。

 

病例二:小心静态类

 

症状还是某个子文件加载后不能卸载,但是当我们再次加载这个子文件的时候,能从log看到之前的子文件被释放了。这是一个轻度内存泄漏的例子,一般不会引起内存飙升直到引起crash等强烈后果,但是我们也不能掉以轻心。

根据之前的经验:不能卸载一定是某个对象被占住了,后续再次加载又能卸载之前的实例,说明前面文件中被占住的资源又被释放了。我们先通过Profiler查看到底是那个对象被占住,然而分析下来看到居然是子文件中创建的所有实例都已经释放了。那么,到底是什么原因呢?

既然实例都已经被释放了,那么只有可能是类定义被占住了。我在这个子文件中用到了Greensock类库的ImageLoader。通过研究它的源码发现这个加载类库采用了与TweenMax类似的插件机制。当我第一次引用ImageLoader定义的时候,它会自动向LoaderMax类注册。也就是说LoaderMax类的静态成员持有ImageLoader定义的引用。

如果这两个类定义都在子应用程序域中,那么随着子文件的卸载,这两个静态类也会被销毁了。但是我在主文件中也包含了LoaderMax类,这个定义会覆盖掉我在子文件中的定义。于是造成的情况就是:一个主应用程序域中的LoaderMax类持有子应用程序域中的ImageLoader类的引用。这就是子文件无法卸载的原因!

解决方法很简单:要么在主文件中也包含ImageLoader类的定义,要么在主文件中删去LoaderMax类。这样我们就解决了一个由于跨域的静态类引用造成的内存泄漏问题。

从这个例子我们还可以总结一下在ActionScript中静态类、静态变量及其衍生的单例的注意事项,这也是和其他编程语言不同的地方:

  • 只要静态类的定义是在子应用程序域里的,那么是可以被卸载的。
  • 静态类、单例的只能保证在同一个应用程序域里的唯一性。也就是说有可能单例不单。
  • 真正保证静态类和单例的唯一性的方法是把它们的定义加入到主应用程序域。

这种静态类之间引用的问题也是唯一让Profiler束手无策的情况,如果以后能在Profiler中直接看到类定义来自哪个应用程序域就更好了。

除此之外还要小心的是静态类的方法可能造成的对象引用问题,比如:Flash组件的FocusManager.setFocus(),以及Flex框架中的StyleManager的样式注册等等。这篇文章详细讨论了Flex模块的卸载问题。

 

病例三:延时删除

 

这个无法卸载的问题来自于我的一个使用Robotlegs和模块插件开 发的子文件。为了让所有mediator执行自己的onRemove()方法,我在ShutdownCommand中将所有视图从contextView 上移除,此外还进行了model和service自己的清理工作。这通常运行良好,能够正确的将模块卸载。但是我却遇到了一个问题,严格来说,这并不是一 个GC的问题。因为我通过trace发现mediator的onRemove()方法并没有执行!

没有执行清理当然就有可能造成内存泄漏,那么到底是什么原因,让我从contextView上移除视图的时候没有触发对应mediator的onRemove()方法呢?

答案是Robotlegs的延时机制。为了兼容Flex框架,mediator的onRemove方法并不是在视图的REMOVED_FROM_STAGE事件监听里执行的,而是延迟了一帧(查看代码)。这样在真正的移除代码执行以前我的视图就已经从stage上移除了,也就过不了330行那个检查。

于是我就只好迁就一下Robotlegs,把子文件从显示列表上移除的时间也延迟了一帧,这样问题就解决了。

从 这几个例子我们可以看出,内存泄漏的病因可能千奇百怪,但归根结底肯定都是某种引用没有被释放的问题。在实际项目中,建议大家一边开发一边就要测试内存泄 漏。不要到了项目的最后阶段再来排查,那样复杂度太高。此外,在引入第三方类库的时候,也要特别注意是否会引起内存泄漏。

 

上面总结了排查内存泄漏的方法,分析了若干可能引起内存泄漏的代码问题。希望对大家有所帮助。如果同学们在自己的项目中也遇到过一些疑难杂症,欢迎留言一起探讨。

这篇关于ActionScript垃圾回收的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/704744

相关文章

离心萃取机废旧磷酸铁锂电池回收工艺流程

在废旧磷酸铁锂电池的回收工艺流程中,离心萃取机主要应用于萃取除杂的步骤,以提高回收过程中有价金属(如锂)的纯度。以下是结合离心萃取机应用的废旧磷酸铁锂电池回收工艺流程: 电池拆解与预处理 拆解:将废旧磷酸铁锂电池进行拆解,分离出电池壳、正负极片、隔膜等部分。破碎与筛分:将正负极片进行破碎处理,并通过筛分将不同粒径的物料分开,以便后续处理。 浸出与溶解 浸出:采用适当的浸出工艺(如二段式逆

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

HotSpot虚拟机的经典垃圾收集器

读《深入理解Java虚拟机》第三版笔记。 关系 Serial、ParNew、Parallel Scavenge、Parallel Old、Serial Old(MSC)、Concurrent Mark Sweep (CMS)、Garbage First(G1)收集器。 如图: 1、Serial 和 Serial Old 收集器 2、ParNew 收集器 3、Parallel Sc

浅谈PHP5中垃圾回收算法(Garbage Collection)的演化

前言 PHP是一门托管型语言,在PHP编程中程序员不需要手工处理内存资源的分配与释放(使用C编写PHP或Zend扩展除外),这就意味着PHP本身实现了垃圾回收机制(Garbage Collection)。现在如果去PHP官方网站(php.net)可以看到,目前PHP5的两个分支版本PHP5.2和PHP5.3是分别更新的,这是因为许多项目仍然使用5.2版本的PHP,而5.3版本对5.2并不是完

Java虚拟机垃圾回收的几个关键问题

20151008 GC的几个关键问题,触发条件,触发的机制 主线是数据的移动,从什么位置到什么位置,移动的条件是什么? 1 垃圾收集在什么时候触发? GC都是在带满了的时候触发的,每次触发都是把不会用的不可达的对象空间回收了,留下还在用的对象。 1) MinorGC的触发是伊甸园空间满的时候 2) FullGC的触发是在老年代满的时候 2 垃圾回收的时候做哪些工作? 1) 一个新的对象new出

水面垃圾检测数据集 3000张 水面垃圾 带标注 voc yolo

数据集概述 该数据集包含3000张图像,专注于水面垃圾的检测。数据集已经按照VOC(Visual Object Classes)和YOLO(You Only Look Once)两种格式进行了标注,适用于训练深度学习模型,特别是物体检测模型,用于识别水面上的各种垃圾。 数据集特点 多样性:包含3000张图像,涵盖了多种类型的水面垃圾,确保模型能够识别各种类型的垃圾。双标注格式:提供VO

基于YOLOv10的垃圾检测系统

基于YOLOv10的垃圾检测系统  (价格90) 包含    ['CardBoard', 'Glass', 'Metal', 'Paper', 'Plastic']     5个类             ['纸板', '玻璃', '金属', '纸张', '塑料'] 通过PYQT构建UI界面,包含图片检测,视频检测,摄像头实时检测。 (该系统可以根据数据训练出的yolov10的权

一文详解go底层原理之垃圾回收

1 前置知识 1.1 三色回收法 三色回收法在gov1.5版本时是主流的gc方式 简单介绍一下流程: 暂停程序执行流程(开启STW)将新创建的对象全部标记为白色从根节点开始遍历,把遍历到的第一层全部改为灰色遍历一次灰色集合,将灰色集合引用对象变为黑色重复上述步骤,知道没有灰色对象清除白色对象结束STW 1.2 STW 上述1.1所说的STW就是指的stop the world,简单的说

proe5.0 config.pro 选项清理垃圾关系

proe5.0 config.pro 选项:  cleanup_drawing_dependencies YES_CS_NOT_REQUIRED c leanup_layout_dependencies YES_CS_NOT_REQUIRED 可以清理所有不应该存在的依赖关系 在某些情况下,图纸、布局和模型可能包含对模型的不需要的默认、幽灵、无效、旧的或遗留引用或者垃圾引用,如何删除这些引用?

Java中的强引用、软引用、弱引用和虚引用于JVM的垃圾回收机制

参考资料 https://juejin.cn/post/7123853933801373733 在 Java 中,引用类型分为四种:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。这些引用类型的主要区别在于它们如何与垃圾回收器(GC)进行交互。 1. 强引用(Stro