本文分析Bindows 1.3类库在IE环境下的内存释放机制。通过对比常规手动断开引用链、清除事件及自定义属性的做法,指出仅靠“仔细”操作难以完全避免复杂脚本的内存增长。深入解析Bindows的dispose方法,包括切断DHTML与脚本对象引用、清理全局缓存及解绑事件,展示了其高效处理内存释放的原理。
IE脚本内存泄漏的常规处理困境
此前曾探讨过IE浏览器中JavaScript脚本导致的内存泄漏问题,经多位网友讨论,大家基本认同了内存泄露现象及其背后的原理。在小型测试用例中,这些方法似乎能有效避免IE脚本的内存泄漏问题。然而近期发现,仅靠“仔细”操作来防止IE脚本内存泄漏极其困难,这是否意味着之前的讨论存在偏差?
所谓的“仔细”,是指在对象相互引用的场景下,当对象被丢弃时(不一定是页面刷新),必须断开彼此之间的引用链,特别是脚本创建的对象与DHTML对象之间的引用;同时需清除HTML元素中所有的自定义属性,移除所有HTML元素的事件处理函数回调,并在数组废弃时尽力删除其内部元素。
最关键的原则是,尽量避免创建冗余的脚本对象和DHTML元素对象。如果通过修改属性即可达到目的,即便过程稍显繁琐,也不应重新生成新的对象。
执行上述步骤后,IE的内存使用增长率虽有下降,但仍无法完全支持复杂脚本的运行(例如达到Bindows的复杂程度),具体表现为以下几点:
一、在脚本执行期间,内存使用量呈现只增不减的趋势;
二、通过最小化IE窗口强制触发垃圾回收(GC),仅能回收物理内存,对虚拟内存无效;
三、当页面跳转(URL改变)至原脚本执行域之外时,内存释放量极少甚至完全不释放;
四、必须关闭IEXPLORE.EXE进程(即关闭所有IE窗口),才能彻底释放IE所占用的内存。
Bindows 1.3内存释放机制实测
近日想起久未关注的Bindows,查看发现其于2月底发布了1.3版本,遂运行主页提供的Demo。效果无需多言,可自行体验,其效率相当高。Demo中还包含类似多维数据显示的GRID组件,甚至支持行和列的表头固定。炫目已是Bindows的一贯特色,在惊叹之余,我注意到应考察Bindows对内存的处理方式。结果令人惊讶:
访问www.bindows.net,IE内存占用约为(28PM+18VM)M,加载demo program后升至(38PM+35VM)M,操作后达(80PM+75VM)M。关闭demo窗口后释放约15M,稳定在(70PM+70VM),切换至google后内存未降。Bindows存在Memory Leak,令人沮丧(555...)。短暂等待后内存恢复至初始水平,证实非IE问题,遂跟踪Bindows在onunload时的处理代码。
如何直接跳转到onunload的代码?这里有一个技巧:先按下Alt+V,u,b(需取消IE选项高级设置中的“禁止脚本调试”,菜单View中才会显示U快捷键选项)。随后立即关闭Bindows的演示Demo窗口,选择VS.NET 2003作为Script调试器,即可直接跳转到onunload的入口。
Bindows dispose方法核心实现
在管理IE中的脚本内存方面,Bindows做得非常周全。复杂对象均实现了完善的dispose方法,其作用是什么呢?
当dispose被调用时,首先切断DHTML对象实例与脚本对象实例之间的引用链;清除全局cache变量中的数据,使用delete关键字;对于通过attachEvent方式导入的事件处理函数,需要进行detach;其他事件处理回调则通过赋null的方式清空;最后切断脚本对象之间的parent或child关系引用链。这里可能存在误解:IE的GC触发机制是不确定的(目前已知确定触发方式仅为最小化IE窗口)。因此,即使完成了上述工作,在页面刚onload时内存也不会立即释放。不过,经过一段时间的使用后,IE使用的内存会减少。所以无需怀疑之前讨论的方法,且除了“切断脚本对象之间的parent或child关系引用链”这一环节外,Bindows的dispose原理和处理方法与我之前的讨论基本一致。
注:PM代表物理内存,VM代表虚拟内存。这两项数据均可在任务管理器中查看。