引用, 强弱引用, 事件机制与垃圾回收的关系及应用法则

来源:互联网 发布:photoshop for linux 编辑:程序博客网 时间:2024/05/18 01:53
经常见朋友们提起 “资源占用” 与 “垃圾回收”机制, 此类情况有常常伴随事件机制与显示列表相关问题,还有诸如此类的强弱引用的问题,关系错综复杂,令人困惑不小。在经过一番整理和测试研究后决定拿出来和大家一起分享一下心得,共同促进天地会的壮大。

先来说说一些基本概念( 不完全遵从于课本,理解不准确请见笑 ):

1.引用类型与值类型的关系:
   值类型属于简单数据类型( Nmber/String/uint...), 具有可复制性,就像一本书可以被发行数万册一样,每本书都出自于同一版,只是相同内容的复制而已;
  引用类型属于复杂数据类型( XML/Sprite/Object...), 具有唯一性,  如同一个人办了很多信用卡,但所有帐号都对应同一个人; 你可以被称为张三/经理/老公, 但这个世界只有一个你,You are the No.1
   引用可以被理解为对象的一个 “标示”,是为了区分和操作对象起来方便一些, 如同每个人的名字一样。

2. 强引用与弱引用:
  强引用也就是我们通常所讲的引用,其存亡直接决定了所指对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示列表中,则此对象会被从内存中释放。
  弱引用除了不决定对象的存亡外,其他与强引用相同。即使一个对象被持有无数个若引用,只要没有强引用指向他,那麽其还是会被清除。没办法,还是 “强哥” 有面子。
  临时变量 < 弱引用 < 强引用 = 字段引用

3. AS3.0 是一个面向对象的事件驱动的高级语言,可见事件在其中的位置。 事件分为 "事件触发者", "事件对象",  "事件接收者 ". 事件可以大大降低各个对象之间的耦合性, 灵活的传递参数,在Caigrom Framework 事件的地位很重要。如果事件触发者没了,那么事件接收者也就不会接收到事件; 如果事件接收者没了, 那就等于事件不会被接收; 事件对象在事件执行完毕后会被释放。
鉴于篇幅这里就不过多的讲述了。

4. 垃圾回收
  垃圾回收是虚拟机中的内存管理机制,是为了清除掉内存中的闲置资源和实现内存碎片的整理。垃圾回收究竟何时运行我没有具体研究,有兴趣的朋友可以去自 行了解下, 不过经过测试发现 :当系统出现异常时( 例如 : IO_Error ) 垃圾回收会自动运行。其实现原则是这样的 : 对象不存在于显示列表,并且不存在指向对象的引用,那麽此对象会被从内存中释放,这句话听起来很简洁,但如果与其他情况混到一起 往往让人无所适从,不知道何种情况下对象才会被释放,下面将展开对此问题的逐步讨论:

     先看一段代码:
       private function loadTest() : void
       {
              var loader : URLLoader = new URLLoader();
               loader.addEventListener( Event.COMPLETE, loadedHandler );
               loader.loader( new URLRequest( "url" ) );

        }

        private function loadedHandler( evt : Event ) : void
        {
               ( evt.target  as Loader ).removeListener(  Event.COMPLETE, loadedHandler );
               // Do others.
         }
         
         函数 loadTest() 中声明了一个临时变量 "loader", 此事件触发者 "loader" 持有对 loadedHandler () 函数的引用,用来处理事件, 我们先来简单分析一下:
      A : loader 何时被回收?
         loader 是个临时变量,此引用是临时的,生存期很短暂,只在 loadTest() 函数内有效, 所以可以理解为 : 不存在指向 "loader" 的引用。但 loader 并不会马上被内存释放, 因为其还没有执行
      完, 当加载完成或IO错误时 loader 才会被释放, 其持有的 loadedHandler() 函数的引用随之消亡。类似此类的还有 建立连接, 打开本地文件之类的情况, 只有执行完毕/关闭连接后 对象才
      可以被释放。

      B : 假设 此段代码 位于 TargetClass 中,  如果 TargetClass 也是被临时创建的, 那麽他何时被释放?
      只有当载入事件执行完毕时 TargetClass 才可以被释放,因为这时 loader 才可以被释放,loader 间接持有 TargetClass 的引用,loader被释放-> TargetClass 的引用 消亡 -> TargetClass 被释放。

      C : 如果  loader.addEventListener( Event.COMPLETE, loadedHandler, false, 0, true ) :  loader 持有 TargetClass 的弱引用, 这时谁先消亡,谁后消亡?
      弱引用不影响到对象的消亡, 所以 : TargetClass 会瞬间被释放, loader 执行完才会被释放, 也不会执行 loadedHandler();

         D:  当 loader 是 TargetClass 中的一个字段( TargetClass 的一个属性/变量) , 他们又将如何消亡?

         private var loader : URLLoader;
         private function loadTest() : void
         {
               loader = new URLLoader();
               loader.addEventListener( Event.COMPLETE, loadedHandler );
               loader.loader( new URLRequest( "url" ) );
        }

        TargetClass 间接 持有自身的引用, loader 执行期内, TargetClass 不能被回收, loader 执行完毕后 两者一起被回收。 象这种 : 对象间接(  不管间接多少层 ) 或直接持有自身的引用
     不会影响对象的释放,即使不清除事件监听和"实现间接的对象" 他们也会被释放。

     E : 如果向上述情况中,loader 采用弱引用监听, 那么谁先被释放?
     此时,TargetClass 会瞬间被释放, loader 执行完毕后被释放。那么有人问了: loader 是TargetClass中的一个字段, 怎么可以 TargetClass 先被释放, 而 loader 后被释放呢?
     其实 loader 只是 TargetClass 持有的一个引用字段, 他并不是 URLLoader 本身( 不能说TargetClass 包含URLLoader), TargetClass 释放后 loader 引用也就消除了, 但已被创建的
     URLLoader 等到执行完毕后才会被释放。

     重复上述原则: 对象不存在于显示列表, 且不存在指向对象的引用,那麽此对象会被从内存中释放. 那么对象如何才算被引用(直接/间接)?
     F : 如果我在 TargetClass 外部 建立一个对于 loader 的引用(  把loader 的作用域公开), 那麽是不是就等于我间接的持有了TargetClass 的引用, TargetClass 不会被释放呢?
       答案是 : TargetClass 会被释放, loader 即使执行完都不会被释放。 TargetClass 被释放时同时释放了 指向 URLLoader 的一个引用 "loader", TargetClass 外部还有一个引用指向 这个  URLLoader, 所以"这个URLLoader" 不会被释放。引用对象的的字段不能算是间接引用对象, 引用对象中的方法才算是间接引用对象( 可以看出对象更强调其行为 ),这样此对象被持有引用, 不会被释放。

    听起来似乎还是很乱, 没关系, 接触多了自然就会搞清楚他们之间的关系, 下面提出几点法则供大家参考:

    1. 尽可能让对象自身的存亡不要影响到别人, 所以尽可能的使用弱引用, 除非你有特殊情况。
    2.对于永远存在的对象, 永远让其持有其他对象的弱引用。 例如 stage.addEventListener( MouseEvent.MOUSE_MOVE, object.moveHandler, false, 0, true );
      3. 养成良好的习惯, 事件执行完一定要移除监听(  清除引用 ), 连接执行完一定要关闭连接.............., 自己产生的垃圾,自己清理,不要乱扔果皮和烟头......   
      4.如果一个对象不会被多次方访问,那么没必要给其分配一个字段引用, 如一些皮肤 , 只要将其添加到显示列表就OK了, 没必要给他个 引用,多一事不如少一事。
      5. 局部性的业务逻辑,尽可能不要用诸如 CairGorm 中的 全局事件, 冒泡法就可以解决, 全局性的逻辑再用全局事件, 局部耦合性可以高一点, 但全局一定要耦合小。
0 0