论坛首页 Web前端技术论坛

Ext&OPOA的内存释放改进 (Ext3.0.0)

浏览 5912 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-09-11   最后修改:2009-09-15
帖子发了好几天,可惜么人回 
好在经过几天的努力,发现这个问题也没那么困难。
目前修改了一些Ext组件,使用sIEve检查发现除form节点外,其它的都可以正常释放掉。
(form节点无法释放好像是IE自身的特性)
不过只是想要个是否可行的答案罢了,现在看来貌似是可行的。

自己做的补丁帖在3楼
由于是做非常简单的可行性测试,所以只有部分标准UI组件的修正,有兴趣的可以回帖交流。

---2009-9-14

-----------------------------传说中的分割线---------------------------------------

最近准备使用Ext3.0.0开发一个one page one application的网站
预先做了一个测试页面,使用动态加载JS,多Tab切换,无iframe。

界面效率问题不用说了,一直就那样,还勉强可以接受。
但在IE下测试内存占用的时候,发现问题真的很严重。
一个新Tab页面,里面有两Tab,每Tab里有Tree、Grid、Chart、Form面板各一个
每多开一个Tab页,内存占用上升9M左右。关闭Tab页释放的内存也极少
开始以为IE本身特性就是这样(不立即释放内存)
但测试开了20来个Tab页,性能下降了一倍左右,内存占用近200M,关闭Tab页也没有释放几M。

于是使用sIEve来检测,发现每开关一个Tab页,都会出现近100来个孤立(Orphan)Dom节点无法释放。
在开关20来次后,总Dom节点达到了近5K的水平
Dom节点无法释放,肯定是有引用它们的JS对象,这些对象也属于无用数据,应当被清除掉才对。

后来上网查找资料,发现Ext2.2有人写过一个补丁,也是做opoa时发现问题并手工修正的:
http://www.extjs.com/forum/showthread.php?t=45782
这个帖的作者是中国人: Location: nanning, china
不过我们要用到Ext3.0的一些新功能,所以不会考虑Ext2.2
并且他好像很久没上过线,也没有其它联系方式可以交流,真的很可惜。

实在查不到可用的解决方案只好自己尝试:
查找各组件的destroy方法,看它是否完全销毁了各种引用
换句话说,看它是否将自己创建的已经无用的对象销毁,是否将自身与其它任何dom节点、组件切断关系。
没有的话就给它打补丁。

接触Ext也没多久,忙了好几天,只修改了几个组件,并且还不知道有没有其它问题。

例如Ext.Panel组件的修改:
Ext.override(Ext.Panel,{
    onDestroy : function(){
        Ext.destroy(
            this.header,
            this.tbar,
            this.bbar,
            this.footer,
            this.body,
            this.bwrap,
			this.dd
        );
		delete this.header;
		delete this.tbar;
		delete this.bbar;
		delete this.footer;
		delete this.body;
		delete this.bwrap;
		delete this.dd;
        Ext.Panel.superclass.onDestroy.call(this);
    }
});

注:原beforeDestroy没有考虑this.dd的销毁,没有彻底清除dom节点,没有最后delete解引用
开始是直接修改beforeDestroy方法的,但发现删除顺序有些问题,造成继承它的一些组件destroy时出错
所以新建了onDestroy方法
这些属性都是在Panel这一层定义的,销毁也应该在这一层进行。

以上示例测试过,发现所有节点都能释放。

另外还尝试过修改Ext.dd、Ext.ux.Portal的释放
由于水平不足,对自己的修改很没信心,就先不帖出来了。

希望这帖子能起到抛砖引玉的作用,大家一起讨论解决方法
(有点私心的说 )
   发表时间:2009-09-11  
继续抛砖。。。

今天继续尝试的时候,发现之前没注意到的问题。
释放变量和dom节点,可以选择在destroy/beforeDestroy/onDestroy中进行。
我一直是直接写在destroy方法中
但修改到Ext.Container中,释放this.items,this.layout,this.ownerCt这3个变量时,发现孤立dom节点更多了。
Ext.override(Ext.Container,{ // Ext.BoxComponent
	destroy	:	function(){ // add
		delete this.items;
		delete this.layout;
		delete this.ownerCt;
		Ext.Container.superclass.destroy.call(this);
	}
});

后面一个个测试发现,Ext.Container中定义的beforeDestroy实际上是在定义的destroy方法之后执行。
由于destroy已经直接解除items引用,导致里面的内容没有经过析构,从而引发了孤立节点。
因为,beforeDestroy的运行,是在Ext.Component的destroy中进行的:
    destroy : function(){
        if(this.fireEvent('beforedestroy', this) !== false){
            this.beforeDestroy();
            if(this.rendered){
                this.el.removeAllListeners();
                this.el.remove();
                if(this.actionMode == 'container' || this.removeMode == 'container'){
                    this.container.remove();
                }
            }
            this.onDestroy();
            Ext.ComponentMgr.unregister(this);
            this.fireEvent('destroy', this);
            this.purgeListeners();
        }
    },

而对destroy进行继承后,实际上是先执行Ext.Container.destroy,到后面再执行Ext.Container.superclass.destroy
从而使得出现了这种现象

为了避免再出现这种顺序问题,可以在Ext.Container.destroy开始就执行superclass.destroy
或者,改为重写beforeDestroy。

Ext自身好像没有规划得很明确,什么时候该放在beforeDestroy中,什么时候放在onDestroy中,什么时候直接继承destroy。


所以暂时想了一个规则,用来避免再次发生这种问题:
基类中销毁直接定义destroy方法,并调用onDestroy(如果没有,可设置为Ext.emptyFn)
子类中继承onDestroy,并执行完自己的工作后调用父类的onDestroy(和C++里析构函数执行顺序一样)
0 请登录后投票
   发表时间:2009-09-14   最后修改:2009-09-15
中间还改了一些使用中发现的BUG,另外还参考了Ext3.0.1的部分修正。
(Ext3.0.1是从某个使用正版的网站上偷下来的……)


ext-patch.js
// ******************************* Bugs fix ***********************************

Ext.override(Ext.chart.Chart, {
	onDestroy: function(){
		this.bindStore(null);
		var tip = this.tipFnName;
		if(!Ext.isEmpty(tip)){
			delete window[tip];
		}
		Ext.chart.Chart.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.form.TextField,{
	autoSize : function(){
        if(!this.grow || !this.rendered){
            return;
        }
        if(!this.metrics){
            this.metrics = Ext.util.TextMetrics.createInstance(this.el);
        }
        var el = this.el;
        var v = el.dom.value;
        var d = document.createElement('div');
        d.appendChild(document.createTextNode(v));
        v = d.innerHTML;
        Ext.removeNode(d);
		d = null;
        v += ' ';
        var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
        this.el.setWidth(w);
        this.fireEvent('autosize', this, w);
    }
});

Ext.override(Ext.Component, {
	onRender : function(ct, position){
        if(!this.el && this.autoEl){
            if(Ext.isString(this.autoEl)){
                this.el = document.createElement(this.autoEl);
            }else{
                var div = document.createElement('div');
                Ext.DomHelper.overwrite(div, this.autoEl);
                this.el = div.firstChild;
            }
            if (!this.el.id) {
                this.el.id = this.getId();
            }
        }
        if(this.el){
            this.el = Ext.get(this.el);
            if(this.allowDomMove !== false){
                ct.dom.insertBefore(this.el.dom, position);
                if (div) {
                    Ext.removeNode(div);
                    div = null;
                }
            }
        }
    }
});
// ************************* improve **********************

(function(){
	Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
		var ml = new Ext.Element(document.createElement('div'));
		document.body.appendChild(ml.dom);
		ml.position('absolute');
		ml.setLeftTop(-1000, -1000);
		ml.hide();

		if(fixedWidth){
			ml.setWidth(fixedWidth);
		}

		var instance = {
			getSize : function(text){
				ml.update(text);
				var s = ml.getSize();
				ml.update('');
				return s;
			},
			bind : function(el){
				ml.setStyle(
					Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
				);
			},
			setFixedWidth : function(width){
				ml.setWidth(width);
			},
			getWidth : function(text){
				ml.dom.style.width = 'auto';
				return this.getSize(text).width;
			},
			getHeight : function(text){
				return this.getSize(text).height;
			},
			// Add by clue
			destroy	:	function(){
				Ext.destroy(ml);
				delete ml;
			}
		};

		instance.bind(bindTo);

		return instance;
	};
})();
// ******************************* Memory Release *****************************

Ext.override(Ext.Component,{ 
	onDestroy	:	function(){ 
		if(this.plugins){
			Ext.destroy(this.plugins);
		}
		Ext.destroy(this.el);
	}
});

Ext.override(Ext.Panel,{
    onDestroy : function(){
		Ext.destroy(
            this.header,
            this.tbar,
            this.bbar,
            this.footer,
            this.body,
            this.bwrap,
			this.dd
        );
		Ext.Panel.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.dd.DragDrop,{ 
	onDestroy	:	Ext.emptyFn,
	destroy : function(){
		this.onDestroy();
        this.unreg();
    }
});

Ext.override(Ext.dd.DragSource,{ 
	onDestroy	:	function(){
		Ext.destroy(this.proxy);
		Ext.dd.DragSource.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.grid.GridDragZone,{
	onDestroy	:	function(){
		Ext.destroy(this.ddel);
		Ext.grid.GridDragZone.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.dd.StatusProxy,{ 
	onDestroy	:	Ext.emptyFn,
	destroy : function(){
		this.onDestroy();
		Ext.destroy(this.anim,this.el,this.ghost);
    }
});

Ext.override(Ext.grid.GridView,{
	destroy : function(){
        if(this.colMenu){
            Ext.menu.MenuMgr.unregister(this.colMenu);
            this.colMenu.destroy();
        }
        if(this.hmenu){
            Ext.menu.MenuMgr.unregister(this.hmenu);
            this.hmenu.destroy();
        }
		this.initData(null, null);
        this.purgeListeners();

        if(this.grid.enableColumnMove){
			delete Ext.dd.DDM.locationCache[this.columnDrag.id];
			Ext.destroy(this.columnDrag,this.columnDrop);
        }

        Ext.fly(this.innerHd).removeAllListeners();
        Ext.removeNode(this.innerHd);

		Ext.destroy(
            this.el,
            this.mainWrap,
            this.mainHd,
            this.scroller,
            this.mainBody,
            this.focusEl,
            this.resizeMarker,
            this.resizeProxy,
            this.activeHdBtn,
            this.dragZone,
            this.splitZone,
            this._flyweight
        );
        Ext.EventManager.removeResizeListener(this.onWindowResize, this);
    }
});

Ext.override(Ext.tree.TreePanel,{ 
	onDestroy : function(){
        if(this.rendered){
            this.body.removeAllListeners();
            Ext.dd.ScrollManager.unregister(this.body);
			Ext.destroy(this.dropZone,this.dragZone,this.innerCt);
        }
        Ext.destroy(this.root);
        Ext.tree.TreePanel.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.grid.HeaderDropZone,{ 
	onDestroy	:	function(){
		Ext.destroy(this.proxyTop,this.proxyBottom);
		Ext.grid.HeaderDropZone.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.menu.Menu,{
	onDestroy : function(){
        Ext.destroy(this.el);
        Ext.menu.MenuMgr.unregister(this);
        Ext.EventManager.removeResizeListener(this.hide, this);
        if(this.keyNav) {
            this.keyNav.disable();
        }
        var s = this.scroller;
        if(s){
            Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
        }
		this.purgeListeners();
		Ext.menu.Menu.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.TabPanel,{ // Ext.Panel
	onDestroy	:	function(){
		Ext.destroy(
			this.stack,
			this.stripWrap,
			this.stripSpacer,
			this.strip,
			this.edge,
			this.leftRepeater,
			this.rightRepeater
		);
		Ext.TabPanel.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.layout.ColumnLayout,{ 
	destroy	:	function(){
		Ext.destroy(this.innerCt);
	}
});

Ext.override(Ext.form.Field,{ 
	onDestroy	:	function(){
		Ext.destroy(this.errorEl,this.errorIcon);
		Ext.form.Field.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.form.TextField,{
	onDestroy	:	function(){
		Ext.destroy(this.metrics);
		if(this.validationTask){
			this.validationTask.cancel();
			this.validationTask = null;
		}
		Ext.form.TextField.superclass.onDestroy.call(this);
	}
});
0 请登录后投票
   发表时间:2009-09-15  
我用的是Ext Gwt[即:Gxt] ...  也碰见效率问题...
    我的解决方法是,尽量使用单例模式... 然后自己写清空代码...
这样做后,第一次加载比较慢, 之后会非常快..

关于内存释放问题一直没研究过,有空研究研究...
0 请登录后投票
   发表时间:2009-09-16  
不错的主题,这是EXT做OAOP的致命伤,好像官方一直没有很好地解决这个问题
0 请登录后投票
   发表时间:2009-09-16   最后修改:2009-09-16
不错的帖子.

3.0.1的官方说明改进了不少,不过搞不到补丁包...


btw,楼主的帖子的分类错了,不在EXT下.
论坛有bug,在blog那边修改后,论坛这边的分类会出问题.
0 请登录后投票
   发表时间:2010-04-30  
楼主我用你的方法释放panel,使用sIEve查看 好象没什么效果啊,内存还是没释放..
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics