原文在这里:Garbage Collection with Flex and Adobe Air

我终于有时间来整理在flexcamp上演讲的东西并写篇博客了。就在flexcamp前一个月,我几乎天天和FlexProfiler争吵而且关系很紧 张(笔者注:结论不一致)。因此我觉得在flexcamp中讲讲性能监测和垃圾回收(GC)是非常合适的话题。没错,想要脱离GC去获得性能提升简直是不 可能的。如果你想改善你的应用的内存管理你必须知道Flashplayer(和adobe AIR)如何管理内存分配和释放。

有许多博客、文章、演讲、等等都介绍过GC和profiling,我在文章底部将他们列举了出来。对于每天使用Flex并且需要来优化内存消耗的初学者和中等经验的开发者来说,我这篇文章是有帮助意义的。

虚拟机(VM)

flashplayer是基于一个虚拟机(精确的来说是2两个,一个是为actionscript2的一个是为actionscript3的),当你需要创建新的对象时虚拟机动态分配内存,例如下面的代码来创建一个新的对象:

var o:Object = new Object();

在启动时VM事先占用(reserve)了一些内存,当上面的代码被执行时VM决定那个对象放在应用内存的什么位置以及它需要占用多少空间。当你创建对象
时,VM可能使用启动时占用的所有内存,如果需要,再向操作系统请求更多的内存。除非你有一个程序仅仅是毫无疑义的创建新对象,正常的应用在执行时必然会
有一个对象变为无用的时候,例如,为了正确的执行程序,它不需要存在了。你将会看到,理解一个对象何时不再被需要是不容易的事情。就现在,让我们加深我们
能够检测到它。在Flash
VM架构,你不能明确的说“删除它‘,内存使用是被VM本身所管理的,VM本身负责检查哪些对象是无用的并擅长他们,这样的机制就叫做垃圾回收。

垃圾回收

那么,作为一个程序员我们能做什么?好的,你可以让垃圾回收变得容易,让我们来看看下图能为我们揭示什么内容。

在启动阶段应用占用了一些要使用的内存,比如说4个块。当你创建了一个对象,VM将第一个空位分配给它。我们说,过一会当o1不再需要是你将它设为
null。你创建一个新的对象o2,你希望o2代替o1。有时它发生,有时它不会发生,这取决于垃圾回收机制,这样一种非常复杂的过程,我们不在这里描
述。这时我们已经得到一个教训:“设置一个对象为null不一定能够释放它占用的内存‘。这取决于flash中已经实现的垃圾回收的方式,GC由重分配触
发而不是由删除触发,这意味着GC周期在你声明new Object()的时候运行而不是你设置它为null时运行。

内存消耗

如果你不得不使用AS3和Flex,你可能知道你可以动态的添加UI元素到图形界面中,通过一个简单的方法叫做addChild(),相反的方法是
removeChild(),它用来移除一个显示元素从UI中。更精确的来说,元素是从视图中删除,但是这不意味着它已经被垃圾回收了。让我来介绍一个简
单的场景给你展示很容易相信removeChild()的事实。

许多Flex应用从服务端加载数据并根据返回的值动态展示它,通常情况视图代码被隔离到一个组件中,经常被称为renderer,它负责展示来自服务器的
数据。我没设计一个非常简单的renderer,它由两个文本字段,嵌入一个VBox中,数据显示是field1和field2,被提供的对象的属性作为
值来源:

  1. xmlns:mx="http://www.adobe.com/2006/mxml"
  2. borderStyle="solid" width="200"
  3. >
  4. >
  5.  
  6. [Bindable] private var _field1:String;
  7. [Bindable] private var _field2:String;
  8. override public function set data(value:Object):void {
  9. _field1 = value.field1;
  10. _field2 = value.field2;
  11. }
  12. ]]>
  13. >
  14. text="{_field1}" />
  15. text="{_field2}" />
  16. >

让我们通过一个简单的函数来模拟数据载入,它返回对象的一个数组:

  1. private function getData():Array
    {
  2. var a:Array = new Array();
  3. for (var i:uint = ; i< ; i++)
    {
  4. var o:Object = new Object();
  5. o.field1 = "field "+Math.random().toString();
  6. o.field2 = "field "+Math.random().toString();
  7. a.push(o);
  8. }
  9. return a;
  10. }

为了渲染数据,我们使用一个简单的函数,来创建一个renderer,设置它的data属性,并添加到VBox中:

  1. private function loadData():void
    {
  2. vbox.removeAllChildren();
  3. var array:Array = getData();
  4. for (var i:int = ; i < array.length; i++)
    {
  5. var rend:MyRenderer = new MyRenderer();
  6. rend.data = array[i];
  7. box.addChild(rend);
  8. i++;
  9. }
  10. }

你看到那个危险的声明了吗?还没有?让我来显示给你,为了模拟一个重复的操作,我添加了一个timer,它会每N秒都调用加载数据的函数:

  1. private function init():void
    {
  2. var t:Timer = new Timer();
  3. t.addEventListener(TimerEvent.TIMER, tick);
  4. t.start();
  5. }

现在,试着运行一下profiler(笔者注:flexbuilder3中的工具),你看到像下图一样一直增长的图形了吗?


恭喜,我们已经发现了一个内存泄漏!内存泄漏发生在重复的动作并且内存消耗持续增长而不是保持恒定。你发现了那个危险的声明了吗?它就是当你创建
renderer是发生的。为什么?因为你认为removeAllChildren()从内存中移除了那些renderers。错误!正像上面所说,这个
方法仅仅从显示树(显示列表)中移除了renderers,事实上,正如你从profiler中看到的,renderers还在那儿,继续消耗着内存。

技术上讲这不是一个内存泄漏,因为这没有任何东西阻止垃圾回收器去清除来自renderers的内存。事实上这种情形下,内存泄漏发生在,当有些对象被认
为使用了renderer(例如一个listener),即使当你从显示树中移除了它。在这个例子中renderers是“自由的‘,可能被垃圾回收,但
是,他们不是这样的,因此,结果仍然与内存泄漏是相同的,不断增长的内存消耗:


有许多技术来解决这种情况,我们来展示两个:缓存renderers和动态缓存。

缓存

让我们假设,你实现知道需要多少renderers,一个方法来解决内存泄漏就是在启动是来创建一个renderers的缓存:

  1. private var cache:Array = new Array();
  2. private function initRenderers():void
    {
  3. for (var i:int = ; i < ; i++)
    {
  4. renderers.push(new MyRenderer());
  5. }
  6. }

我们于是可以这样修改我们的loadData方法:

  1. private function loadData():void
    {
  2. container.removeAllChildren();
  3. var array:Array = getData();
  4. for (var i:int = ; i < array.length; i++)
    {
  5. var rend:MyRenderer = cache[i];
  6. rend.data = array[i];
  7. container.addChild(rend);
  8. i++;
  9. }
  10. }

正如你所见,我们不再创建新的renderers,而是在缓存中查找一个。

很多情况下,你事先不知道多少数据从服务器返回,而且不知道你需要多少renderers,这时你需要一个动态缓存。

动态缓存

动态缓存是基于一个弹性的机制,你有一个地方可以来查找一个renderer:如果缓存中有一个,那就返回它,否则一个新的被临时创建,最好来看看下面的代码:

  1. public class MyRendererCache extends Object
    {
  2. private static var cache : ArrayCollection = new ArrayCollection();
  3. public function MyRendererCache()
    {
    super();
  4. for ( var x : Number = ; x < ; x ++ )
    {
  5. cache.addItem( new MyRenderer() );
  6. }
  7. }
  8. public static function getRenderer() : MyRenderer
    {
  9. var renderer : MyRenderer;
  10. if ( cache.length <= )
    {
  11. renderer = new MyRenderer();
  12. }
    else
    {
  13. renderer = cache.removeItemAt( ) as MyRenderer;
  14. }
  15. return renderer;
  16. }
  17. public static function setRenderer( renderer : MyRenderer ) : void
    {
  18. cache.addItem( renderer );
  19. }
  20. }

在构造函数中,你操作缓存用最小数目的renderers,比如说2个。这个缓存有两个静态方法,getRenderer和setRenderer,第一
个是来获得一个renderer,第二个是当结束时返还给它。这种方式下内存中renderers的数量可能增长超过最小的数目,但是仅是临时的,因为
GC会在他们当不在被引用时删除他们。一个重要的问题是跟setRenderer有关,当你不再需要一个renderer时,你必须把它返回给
cache,否则我们又陷入内存泄漏中,如上解释的那样。为了达到这个目的,我们来利用renderer的remove事件,remove事件是当一个
UI元素被从显示列表中移除时被触发。例如当我们调用removeAllChildren(),这样的事件对于每一个renderer来说都会被触发。我
们可以这样修改renderer:

  1. xmlns:mx="http://www.adobe.com/2006/mxml"
  2. borderStyle="solid" width="200"
  3. remove="onRemove()"
  4. >
  5. private function onRemove():void {
  6. MyRendererCache.setRenderer(this);
  7. }
  8. ....
  9. >

如果现在来运行profiler,你会注意到,内存增长到一个给定的点并且保持稳定,如下图所示:

恭喜,你已经解决了内存泄漏!

建议性GC

除了偏向于自动的GC过程,adobe还允许程序员来建议干涉性的GC。这个命令在flash.system包中的System.gc(),通过这篇文章
“强制垃圾回收过程‘,但是在我的经验来看,这仅仅是一个模糊的干涉建议,它能解决某些情况,因此在开始时它值得一试,当你需要一个快速的方法来节省一些
内存时。

flex 垃圾回收的更多相关文章

  1. flex 强制垃圾回收

    java和flash的垃圾回收都是一个比较热门的话题,今天我也用一个例子来测试下flash的强制垃圾回收.主要用到的而一个类是LocalConnection. 在Flash player的debug版 ...

  2. 关于AS3的垃圾回收

    FlashPlayer运行GC(Gabage Collection)的时间并不固定,它会根据你的内存的占用情况来决定运行GC的时间.它会根据用户机器的内存值来设定一个阀值,然后将程序的占用内存量保存在 ...

  3. as3垃圾回收机制

    as3垃圾回收机制 垃圾回收机制详解 能力越大责任越大,这对actionscript3.0来说一点没错.引入这些新控件带来一个副作用:垃圾收集器不再支持自动为你收集 垃圾等假设.也就是说Flash开发 ...

  4. Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

    执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...

  5. 修改session垃圾回收几率

    <?php //修改session垃圾回收几率 ini_set('session.gc_probability','1'); ini_set('session.gc_divisor','2'); ...

  6. .NET面试题系列[5] - 垃圾回收:概念与策略

    面试出现频率:经常出现,但通常不会问的十分深入.通常来说,看完我这篇文章就足够应付面试了.面试时主要考察垃圾回收的基本概念,标记-压缩算法,以及对于微软的垃圾回收模板的理解.知道什么时候需要继承IDi ...

  7. .net垃圾回收机制编程调试试验

    1. 什么是CLR GC? 它是一个基于引用跟踪和代的垃圾回收器. 从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被 ...

  8. Java垃圾回收

    垃圾收集算法 引用计数 堆中的每个对象都有一个引用计数,当对象被引用时引用计数加1,当对象的引用被重新赋值或超出有效区域时引用计数减1,当一个对象被回收后,它所引用的对象的引用计算减1.当一个对象的引 ...

  9. JavaScript具有自动垃圾回收机制

    JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存.   正常的生命周期:     局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...

随机推荐

  1. Python 矩阵(线性代数)

    Python 矩阵(线性代数) 这里有一份新手友好的线性代数笔记,是和深度学习花书配套,还被Ian Goodfellow老师翻了牌. 笔记来自巴黎高等师范学院的博士生Hadrien Jean,是针对& ...

  2. RabbitMQ交换器Exchange介绍与实践

    RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...

  3. idea无法使用注解@Data解决方法

    @Data相关依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ...

  4. AFNetworking2.0源码解析<二>

    本篇我们继续来看看AFNetworking的下一个模块 — AFURLRequestSerialization.   AFURLRequestSerialization用于帮助构建NSURLReque ...

  5. python面向对象--类的装饰器

    # def deco(obj): # print("=====",obj) # obj.x=1 # return obj # @deco#===> test = deco(t ...

  6. 牛客练习赛33 B tokitsukaze and RPG (类埃筛)

    链接:https://ac.nowcoder.com/acm/contest/308/B 来源:牛客网 tokitsukaze and RPG 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/ ...

  7. shell安装mysql,连接数据库,创建数据库

    https://blog.csdn.net/yhflyl/article/details/83061126 https://blog.csdn.net/wyl9527/article/details/ ...

  8. QKeyEvent

    #include<QKeyEvent>键盘事件由两个重载函数,实现即可:  void keyReleaseEvent(QKeyEvent *event);  void keyPressEv ...

  9. Python之常用模块一(主要RE和collections)

    一.认识模块  什么是模块:一个模块就是一个包含了python定义和声明的文件,文件名就是加上.py的后缀,但其实import加载的模块分为四个通用类别 : 1.使用python编写的代码(.py文件 ...

  10. ZROI CSP-S失恋测(1)

    传送门 写在前面:为了保护正睿题目版权,这里不放题面,只写题解. "怎么大家一个暑假不见都变菜了啊."--蔡老板 A 考虑一个\(nk^2\)的dp,按\(w_i\)排序,则每个组 ...