`
linuxstuding
  • 浏览: 1231925 次
文章分类
社区版块
存档分类
最新评论

关于Ext内存泄漏的部分心得

 
阅读更多

首先说明下,本帖所描述的泄漏是指JS运行中的释放问题,它们大多数在页面关闭时都能释放掉。
----
内存释放在使用Ext开发OPOA系统时显得尤为重要
去年开始接触Ext开发,中间花了很多时间来解决内存泄漏。
最开始Ext还是3.0版,泄漏得一塌胡涂,也没什么处理头绪,只知道在onDestroy里加delete语句,用sIEve看效果。
而Ext3.1有了一个很大的进步,基本上绝大多数组件都没有泄漏了。但并不意味着Ext没泄漏,做出的页面也没泄漏。JS是自动回收的,而只要有一个引用没有释放就可能导致一大片JS对象及Dom节点无法释放,所以迟早还是会面对这个问题。

本人不擅长表达,就直接列心得吧。

一、泄漏的分类
1. 组件创建后未销毁(Ext层面)
这里说的组件一般是指继承自Ext.Component的,它在创建时会注册到Ext.ComponentMgr中,不调用destroy方法是不会从中移除的,所以它永远不会被释放。
大部分组件是放置于Container中,Container销毁会将子组件一起销毁,是不存在这个问题。但当它是Ext.Window(没有父容器)或手工render的组件(例如用模板画html,再render到指定Dom节点)时,如果不主动销毁问题就发生了。

2. 组件自身有泄漏或使用不当(JS&Dom层面)
目前Ext原生组件的泄漏已经很少了,但自己扩展的组件不注意的话会造成浏览器无法释放的泄漏。
(参考:http://www.ibm.com/developerworks/cn/web/wa-memleak/index.html)
而使用不当是指破坏了组件内部结构,导致无法完全释放。

3. JS对象泄漏(JS层面)
JS是有自动回收机制,但它也只会回收你用不到的对象(或者说你已经不可能访问到的对象)
所以,如果在一个长时间运行的JS代码中,你创建了很多属性或对象,并且一直保存着它们的引用,也会使内存增长。
正确的做法是,不用的属性就要delete掉,不用的对象也要解除引用。(delete引用的属性或置变量为null)

看似很少,积少成多也不得了,在1,2类泄漏解决完后,就要面对这样的问题了。

4. 浏览器自身比较恶心的bug
虽然JS&Dom循环引用无法释放也算是浏览器的bug,但好像不只是IE独有,所以没有归到此类。
目前我已经知道的IE的两个很恶心的bug:
1. IE Object leaks
http://www.sencha.com/forum/showthread.php?89317-quot-IE-object-leaks-quot-What-s-going-on
简单点描述,就是IE下,将JS对象的属性delete掉后,属性引用的对象是会解除引用,但属性本身占用的内存不会释放。虽然增长很少,但在极端情况也绝不能无视。

2. IE 8 bug
http://www.javaeye.com/topic/617997
(一点题外话,想不通为什么这帖被评为隐藏帖,大家都这么高手了?国内还真没看到什么人提起过这问题)
在IE8下,form, button, input, select, textarea, a, img, object这些Dom节点,只要创建了就不会被释放掉(其实在IE6/7下,form节点也会有问题)。对这个真的很无语。。。

二、泄漏检查
1. 组件泄漏
这个比较简单,可以写个函数记录Ext.ComponentMgr.all(Ext.util.MixedCollection)中的组件列表,从而判断哪些组件还没有被销毁。

2. 组件内部泄漏
这个就要用sIEve查看了。创建、销毁组件,看Dom列表有无没释放的。

3. JS对象泄漏
当有个内存无法释放的问题,使用sIEve又检查不出Dom节点泄漏,一般就是JS对象的问题了
只能用笨办法:逐步排除
一种是使用Firefox + firebug,直接展开JS对象,查找属性有没有无限制增长。
再一种就是使用IE6/7,调快逻辑执行,长时间运行,记录任务管理器中显示的虚拟内存(不能用其它浏览器,经测试FF和Chrome缓存很厉害,很难测出增长。也不能用sIEve,它监控内存及dom数量时也会造成内存增长,IE8有些bug会影响判断),看有无无限制增长。

4. 浏览器自身bug
对于IE Object leaks,同JS对象泄漏检测。
IE8的,据我测试,sIEve用的是IE7内核,所以无能为力,也是同3的方法。

三、定位并解决
1. 组件泄漏
找到没销毁的,扩展onDestroy,在其中销毁掉。

2. 组件内部泄漏
定位泄漏的Dom节点关联的代码,查检有没有调用removeNode移除,有没有循环引用
具体的不好讲,原因非常多,可以边改边用sIEve看效果。
注意: 请开启Ext.enableListenerCollection = true;这个配置,以便Ext自动回收孤立Dom节点上的事件。默认只会将孤立节点从Ext.elCache中移除,而不会清理事件,可能会导致泄漏。另外需注意Ext每30秒才清理一次,注意分辨。

3. JS对象泄漏
这个没啥办法。。。只能调快操作长时间运行查看平均增长,然后一步步改代码排除了……

4. 浏览器自身bug
IE Object leaks:
参考Ext的解决方案,将对象for in循环复制一份,替换旧的。
IE 8 bug:
尽量避免重复创建form, button, input, select, textarea, a, img, object这些Dom节点,能替换就替换,能复用就复用。

关于内存泄漏的解决方法,以上几乎没写什么有用的东西,说实话,我也不知道该写些什么。
因为我处理的内存泄漏大部分都是排除法定位并解决的,现在除了sIEve能查看Dom节点泄漏,没啥好用的工具能检查与之相关的JS对象的情况。从而导致只能靠蒙来找到造成泄漏的代码。

四、一些泄漏实例
1. 组件泄漏
举些常见的例子
a) 弹出窗口未销毁

Js代码 复制代码收藏代码
  1. Ext.ns("Ext.ux");
  2. Ext.ux.MyWindow=Ext.extend(Ext.Window,{
  3. closeAction:"hide",
  4. modal:true,
  5. initComponent:function(){
  6. this.buttons=[{
  7. text:"确定",
  8. handler:function(){
  9. this.fireEvent("confirm");
  10. this.hide();
  11. },
  12. scope:this
  13. }];
  14. Ext.ux.MyWindow.superclass.initComponent.apply(this,arguments);
  15. }
  16. });
  17. Ext.ux.MyComponent=Ext.extend(Ext.Panel,{
  18. initComponent:function(){
  19. //它创建出来,没有父容管理,也没有主动销毁
  20. this.win=newExt.ux.MyWindow({
  21. width:300,
  22. height:400,
  23. title:"配置xxx",
  24. html:"内容xxx"
  25. });
  26. this.win.on("confirm",this.confirmCfg,this);
  27. this.tbar=[{
  28. text:"设置xxx",
  29. handler:function(){
  30. this.win.show();
  31. },
  32. scope:this
  33. }];
  34. Ext.ux.MyComponent.superclass.initComponent.apply(this,arguments);
  35. },
  36. confirmCfg:function(){
  37. //dosomething
  38. }
  39. });
  40. vartest=newExt.ux.MyComponent({
  41. title:"测试面板",
  42. html:"test",
  43. renderTo:Ext.getBody()
  44. });
  45. //创建&销毁
  46. test.destroy();
  47. test=null;



b) 创建出来未使用,也未销毁

Js代码 复制代码收藏代码
  1. Ext.ns("Ext.ux");
  2. Ext.ux.MyComponent=Ext.extend(Ext.Panel,{
  3. initComponent:function(){
  4. this.btnA=newExt.Button({
  5. text:"模式A",
  6. handler:function(){}
  7. });
  8. this.btnB=newExt.Button({
  9. text:"模式B",
  10. handler:function(){}
  11. });
  12. //btnA与btnB必有一个未纳入Toolbar管理,也未主动销毁
  13. this.tbar=[this.mode==="a"?this.btnA:this.btnB];
  14. Ext.ux.MyComponent.superclass.initComponent.apply(this,arguments);
  15. }
  16. });
  17. //创建&销毁
  18. vartest=newExt.ux.MyComponent({
  19. title:"测试面板",
  20. html:"test",
  21. renderTo:Ext.getBody()
  22. });
  23. test.destroy();
  24. test=null;



c) 直接render到dom子节点中,未显式销毁

Js代码 复制代码收藏代码
  1. Ext.ns("Ext.ux");
  2. Ext.ux.MyComponent=Ext.extend(Ext.BoxComponent,{
  3. tpl:"{text}:<divclass='combo'></div>",
  4. afterRender:function(){
  5. Ext.ux.MyComponent.superclass.afterRender.apply(this,arguments);
  6. this.setCombo();
  7. },
  8. update:function(){
  9. Ext.ux.MyComponent.superclass.update.apply(this,arguments);
  10. this.setCombo();
  11. },
  12. setCombo:function(){
  13. //this.combo每次更新时都创建了新实例,没有销毁旧的,组件销毁时也未销毁它。
  14. this.combo=newExt.form.ComboBox({
  15. store:newExt.data.ArrayStore({
  16. fields:['id','mode'],
  17. data:[
  18. ['1','mode1'],
  19. ['2','mode2']
  20. ]
  21. }),
  22. valueField:"id",
  23. displayField:'mode',
  24. mode:'local',
  25. triggerAction:'all',
  26. emptyText:'请选择模式',
  27. selectOnFocus:true
  28. });
  29. this.combo.render(this.el.child("div.combo"));
  30. }
  31. });
  32. //创建&销毁
  33. vartest=newExt.ux.MyComponent({
  34. data:{
  35. text:"请选择模式"
  36. },
  37. renderTo:Ext.getBody()
  38. });
  39. test.destroy();
  40. test=null;



2. 组件内部泄漏
这一类例子不太好举,Ext自身几乎不存在这种泄漏了,就以excanvas为例吧:

Js代码 复制代码收藏代码
  1. /**
  2. *Publicinitializesacanvaselementsothatitcanbeusedascanvas
  3. *elementfromnowon.Thisiscalledautomaticallybeforethepageis
  4. *loadedbutifyouarecreatingelementsusingcreateElementyouneedto
  5. *makesurethisiscalledontheelement.
  6. *@param{HTMLElement}elThecanvaselementtoinitialize.
  7. *@return{HTMLElement}theelementthatwascreated.
  8. */
  9. initElement:function(el){
  10. if(!el.getContext){
  11. el.getContext=getContext;//给Dom节点添加了方法属性,造成dom->js引用
  12. //Removefallbackcontent.Thereisnowaytohidetextnodessowe
  13. //justremoveallchildNodes.Wecouldhideallelementsandremove
  14. //textnodesbutwhoreallycaresaboutthefallbackcontent.
  15. el.innerHTML='';
  16. //donotuseinlinefunctionbecausethatwillleakmemory
  17. el.attachEvent('onpropertychange',onPropertyChange);//添加了事件,造成dom->js引用
  18. el.attachEvent('onresize',onResize);
  19. varattrs=el.attributes;
  20. if(attrs.width&&attrs.width.specified){
  21. //TODO:useruntimeStyleandcoordsize
  22. //el.getContext().setWidth_(attrs.width.nodeValue);
  23. el.style.width=attrs.width.nodeValue+'px';
  24. }else{
  25. el.width=el.clientWidth;
  26. }
  27. if(attrs.height&&attrs.height.specified){
  28. //TODO:useruntimeStyleandcoordsize
  29. //el.getContext().setHeight_(attrs.height.nodeValue);
  30. el.style.height=attrs.height.nodeValue+'px';
  31. }else{
  32. el.height=el.clientHeight;
  33. }
  34. //el.getContext().setCoordsize_()
  35. }
  36. returnel;
  37. },
  38. //......
  39. /**
  40. *Thisfuntionisassignedtothe<canvas>elementsaselement.getContext().
  41. *@this{HTMLElement}
  42. *@return{CanvasRenderingContext2D_}
  43. */
  44. functiongetContext(){
  45. //调用getContext,在dom上添加了js对象引用,造成dom->js引用
  46. returnthis.context_||
  47. (this.context_=newCanvasRenderingContext2D_(this));
  48. }
  49. //.....
  50. /**
  51. *ThisclassimplementsCanvasRenderingContext2Dinterfaceasdescribedby
  52. *theWHATWG.
  53. *@param{HTMLElement}surfaceElementTheelementthatthe2Dcontextshould
  54. *beassociatedwith
  55. */
  56. functionCanvasRenderingContext2D_(surfaceElement){
  57. this.m_=createMatrixIdentity();
  58. this.mStack_=[];
  59. this.aStack_=[];
  60. this.currentPath_=[];
  61. //Canvascontextproperties
  62. this.strokeStyle='#000';
  63. this.fillStyle='#000';
  64. this.lineWidth=1;
  65. this.lineJoin='miter';
  66. this.lineCap='butt';
  67. this.miterLimit=Z*1;
  68. this.globalAlpha=1;
  69. this.canvas=surfaceElement;//在JS对象上引用了dom节点,这里算是一个循环引用。
  70. varel=surfaceElement.ownerDocument.createElement('div');
  71. el.style.width=surfaceElement.clientWidth+'px';
  72. el.style.height=surfaceElement.clientHeight+'px';
  73. el.style.overflow='hidden';
  74. el.style.position='absolute';
  75. surfaceElement.appendChild(el);
  76. this.element_=el;//同样,JS对象引用dom节点
  77. this.arcScaleX_=1;
  78. this.arcScaleY_=1;
  79. this.lineScale_=1;
  80. }



3. JS对象泄漏
在前面已经介绍过Ext.lib.Ajax存在的属性未移除情况,如果想要更详细,可以看这里:
http://www.sencha.com/forum/showthread.php?103148-OPEN-1099-Tiny-memory-increase-in-Ext.lib.Ajax-so-tiny...

4. 浏览器bug
IE Object leaks,参考Ext作法:

Js代码 复制代码收藏代码
  1. if(Ext.isIE){
  2. vart={};
  3. for(eidinEC){
  4. t[eid]=EC[eid];
  5. }
  6. EC=Ext.elCache=t;
  7. }


IE 8 bug,没啥说的,bug描述得很清楚,怎么避免各显神通吧

PS:有个firefox相关的工具可以查看JS运行状态,非常详细非常专业,但我研究了一天没弄懂
有会用的人发发心得吧。。。
http://www.softwareverify.com/javascript/memory/index.html
点页面右边的eval获取评估版注册码。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics