GC算法和IE中JS内存泄露

    一点总结
Lisp :对现代软件开发技术贡献最大的语言
      •垃圾收集
      •数据结构
      •人工智能
      •并行处理
      •虚拟机技术
      •元数据技术
      •……
J.McCarthy
             Lisp之父
             人工智能之父




M.L.Minsky
1969 图灵奖得主
三大传统算法:Reference Counting (引用计数)


count:               循环引用问题
   3
          obj        (从前有座山,山上有座庙,庙里有个老和尚,
                     老和尚在给小和尚讲故事,讲的啥?
                     从前有座山,山上有座庙…)



  v1      v2    v3
三大传统算法:Mark-Sweep (标记-清除)



         Q:被使用吗         Q:被使用吗         Q:被使用吗   Q:被使用吗
         A: yes, 标记为1   A: yes, 标记为1   A: no    A: yes
Step:1


           V1             V2            V3        …




                                       未被标记,回   未被标记,回
                                       收        收
Step:2

           V1             V2            V3        …
三大传统算法:Mark-Sweep (标记-清除)



第一个实用完善的GC算法
J.McCarthy 1960 用于lisp



step 1:标记被使用的对象
step 2:清除未被标记的对象



没有循环引用问题
效率问题:早期lisp GC时间占到系统总运行时间的40%
三大传统算法: Copying (复制)


      using            using    using

运行
      obj1     obj2    obj3     obj4




       using            using    using

GC时
       obj1     obj2    obj3     obj4




                                         using   using   using
GC后
               obj2                      obj1    obj3    obj4
三大传统算法: Copying (复制)


天才的想法
          •   堆空间平均分成两部分,运行程序只使用一部分
          •   回收时,使用中的对象复制到另一部分



          1. 效率高
          2. 没有内存碎片




               浪费了一半的空间!!!
理想中的垃圾回收



     1. 不暂停程序运行
     2. 不占用大量的内存
     3. 不占用大量的CPU资源



现代算法都是在三大传统算法基础上努力达到上述三点要求
Mark-Compact (标记-整理)

结合Mark-Sweep and Copying


step 1: 标记被使用的对象
step 2: 未标记对象和标记对象向相反方向移动
step 3: 释放未标记对象



•   没有内存浪费
•   效率不错
•   没有内存碎片
Incremental Collecting (增量收集)
基于Mark-Sweep and Copying


实时性较好


M.L.Minsky D.E.Knuth 做了早起研究

G.L.Steele 1975 Multiprocessing compactifying garbage
collection (多进程整理垃圾收集)

 H.G.Baker 1978 List Processing in Real Time on a Serial
Computer (串行计算机上的实时表处理技术)
  阐述了多进程环境下的增量收集算法
Generational Collecting (分代)



    使用统计学     针对不同的内存对象寿命使用不同的策略




1983 H.Lieberman, C.Hewitt
A real-time garbage collector based on the lifetime of objects
那些使用垃圾回收的语言

 * 1960 lisp
 * 1964 Simula
 * 1969 Smalltalk
 * 1970 Prolog
 * 1973 ML
 * 1975 Scheme
 * 1983 Modula-3
 * 1986 Eiffel
 * 1987 Haskell
   ......
 * java, .net, javascript
   ......
浏览器JS引擎GC算法现状



•   截止2008 所有现代浏览器使用Mark-Sweep, even IE
•   IE <= 8 BOM and DOM are COM, COM使用引用计数
•   IE9 makes BOM and DOM objects into true JS object




IE: window.!CollectGarbage()
Opera (>=7): window.opera.collect()
浏览器JS引擎 GC算法现状



•   截止2008 所有现代浏览器使用Mark-Sweep, even IE
•   IE <= 8 BOM and DOM are COM, COM使用引用计数
•   IE9 makes BOM and DOM objects into true JS object




IE: window.!CollectGarbage()
Opera (>=7): window.opera.collect()
何时执行GC



IE6                IE7
一组阀值:
                   默认阀值 同IE6
  {
    256个变量           GC回收率 < 15%,defalut * 2
    4096个对象或数组       GC回收率 > 85%,default还原
    64K string
  }

达到其一就执行GC
IE中JS内存泄露现象
之一:循环引用泄露


  IE(<=8)中JS对象和COM之间产生循环引用时
  IE7修复了此问题
  所以,集中体现在IE6中


              循环引用
              造成泄露
IE中JS内存泄露现象
之一:循环引用泄露


  IE(<=8)中JS对象和COM之间产生循环引用时。
  IE7修复了此问题,所以集中体现在IE6中




            循环引用
            造成泄露




                               断开循环引用
IE中JS内存泄露现象
之一:循环引用泄露


  IE(<=8)中JS对象和COM之间产生循环引用时。
  IE7修复了此问题,所以集中体现在IE6中

       闭包引起的循环引用
IE中JS内存泄露现象
   之一:循环引用泄露


       IE(<=8)中JS对象和COM之间产生循环引用时。
       IE7修复了此问题,所以集中体现在IE6中

            闭包引起的循环引用




google map api 提供了一个函数 用于在页面unload事件中解决闭包带来的
内存泄露问题
IE中JS内存泄露现象

    之二: Cross-Page Leaks   节点插入顺序

          节点插入顺序




为了child能够知道parent的信息,IE创建了一个临时scope对象,
而这个对象泄露了。页面跳转不释放。
IE中JS内存泄露现象

    之二: Cross-Page Leaks    节点插入顺序

          节点插入顺序




                      微软赖皮行径一


为了child能够知道parent的信息,IE创建了一个临时scope对象,
而这个对象泄露了。页面跳转不释放。
IE中JS内存泄露现象

   之三: innerHTML




造成泄露                      正确做法
IE中JS内存泄露现象


之四: Pseudo-Leaks 伪泄露




                  页面销毁时释放
IE中JS内存泄露现象

                       微软赖皮行径二

之四: Pseudo-Leaks 伪泄露




                  页面销毁时释放
建议




•   使用var定义变量,避免错误的定义全局变量
•   全局变量没用时置为null
•   正确使用delete,删除一些无用属性
•   正确使用try catch,确保去除无用引用的代码能够被执行
•   open出来的窗口即使close 后window对象仍存在的,记得删除引用
•   frame和iframe的情况同上
ExtJS1.1 中事件造成的泄露问题




Memory Leak is an important thing, especially in OPOA
ExtJS1.1 中事件造成的泄露问题


节点移除
ExtJS1.1 中事件造成的泄露问题


节点移除
ExtJS1.1 中事件造成的泄露问题


              事件绑定


step 1:Ext.EventManager.on

                             1. 将fn包装得到h
                             2. 将h放到fn的属性中
                             3. 将h包装得到wrappendFn
step 2:Ext.lib.Event.on
                             4. 将h、wrapFn存储在静态变量
                             5. 将wrappendFn绑定到el
ExtJS1.1 中事件造成的泄露问题



事件移除


Element.removeListener
Element.removeAllListener
ExtJS1.1 中事件造成的泄露问题

              事件移除      Element.removeListener

Element.removeListener( fn ) //原始fn

                               1. 从fn的_handlers中删除h的引用
                               2. 对h调用
                                 Ext.lib.Event.removeListener




Ext.lib.Event.removeListener

                               1. 找到缓存在listeners中的数据
                               2. 从listeners将缓存删除
ExtJS1.1 中事件造成的泄露问题

              事件移除      Element.removeListener

Element.removeListener( fn ) //原始fn

                               1. 从fn的_handlers中删除h的引用
                               2. 对h调用
                                 Ext.lib.Event.removeListener




Ext.lib.Event.removeListener

                               1. 找到缓存在listeners中的数据
                               2. 从listeners将缓存删除
ExtJS1.1 中事件造成的泄露问题

              事件移除      Element.removeAllListener


Element.removeAllListener

                              1. 取得el上的所有listener
                              2. 对每一个事件函数调用
                                 removeListener


this.getListeners( el, eventName );//bug

                              1. 从listenrs中获得el绑定的h
                              2. 对h调用
                                 Ext.lib.Event.removeListener
ExtJS1.1 中事件造成的泄露问题

               事件移除     Element.removeAllListener


this.getListeners( el, eventName );//bug

                               1. 取得el上的所有listener
                               2. 对每一个事件函数调用
                                  removeListener




相比removeListener少做了件事情:没有删除掉fn上缓存的h

后果:fn引用h, h引用el

      • IE6下造成泄漏
      • fn为非local变量时 el不会销毁
ExtJS1.1 中事件造成的泄露问题

               事件移除     Element.removeAllListener


this.getListeners( el, eventName );//bug

                               1. 取得el上的所有listener
                               2. 对每一个事件函数调用
                                  removeListener



 解决方案
    1. 事件绑定时缓存fn
    2. 升级到高版本:ExtJS2.2中解决了该问题

ExtJS1.1组件销毁时没有对生成的所有元素做销毁——逐个打补丁
ref



http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol
          /dnwebgen/ie_leak_patterns.asp
http://blogs.msdn.com/b/ericlippert/archive/2003/09/17/53028.aspx
http://blogs.msdn.com/b/ericlippert/archive/2003/09/17/53038.aspx
http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html
http://www.ituring.com.cn/article/details/436
http://blog.stchur.com/2007/05/16/ie-innerhtml-memory-leak/
GC算法和IE中JS内存泄露

GC算法和IE中JS内存泄露