实战Hybird app:内存溢出与优化
pheongap项目:http://www.nduoa.com/apk/detail/646816
主要的问题:
heap过大,内存低性能差的机子上引起奔溃,直接退出
关于web app的优化,不仅仅只是js方面,包括HTML布局嵌套,CSS的属性使用,数据的读取,还有浏览器的重排与回流之类的这里就不讨论了,
本章涉及的是脚本代码引发的性能问题,更进一步说就是闭包带来的内存泄露
关于性能:
首先我不得不承认一个事实,移动端的性能跟PC端,那完全不是一回事
比如用innerHTML绘制大段的HTML结构,之后同步获取生成HTML中的ID节点,结果不存在
这种问题在单页面模拟多页面,动态创建DOM的时候,尤为明显
var element = $('<div id = "aaron">...填充大量结构...</div>');
$(root).html(element)
$('#aaron') //为空
- 这个是很简单的一段代码,按照常规的认识,JS主线程与GUI的渲染线程是互斥的,所以在执行JS的时候,GUI应该就是挂起的, 同理执行GUI的时候亦然, 因为JS可以动态操作节点,所以如果我们在GUI绘制的时候做操作明显就会打乱了,所以互斥的解释也合理
- 但是实际上这样并不能直接获取到$('#aaron'),PC上基本不会出现,常规的办法都是加setTimeout
- 实际上由于setTimeout的机制,所以也是不准确的,当然我已经有一个比较完美的方式解决
关于JavaScript内存管理:
原文:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
JavaScript会给开发者一个错觉:可以不用考虑内存管理
现代浏览器已经够聪明了,从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
所以引用计数收集与循环引用之类的都不再是问题了,过去导致内存泄漏的许多经典模式在现代浏览器中以不再导致泄漏内存。
但是,如今有一种不同的趋势影响着内存泄漏。许多人正设计用于在没有硬页面刷新的单页中运行的 Web 应用程序。在那样的单页中,从应用程序的一个状态到另一个状态时,很容易保留不再需要或不相关的内存。
典型的就是: 单页面模拟多页面的行为
简单的内存管理测试
视图:

HTML结构:
<button id="start_button">Start</button>
<button id="destroy_button">Destroy</button>
脚本代码:
var Leaker = function() {
this.name = 'aaron'
};
$("#start_button").click(function(){
leak = new Leaker();
});
$("#destroy_button").click(function(){
leak = null;
});
点击Start 产生一个对象leak = new Leaker();
点击Destroy 销毁这个对象 观察下内存中变化(工具后面会提到)
点击Start ,产生一个对象

点击Destroy,对象销毁

那么这个图很形象的说明了,#Delata释放了一个实例,就是内存被回收了
如果不做任何处理,那么这个对象leak始终最存在整个生命周期内(全局上下文的情况)
如果leak = null,内存确实是由浏览器GC 自动给回收了
闭包引起的内存泄漏:
代码:内部增加了一个定时器,递归调用
var count = 0;
var Leaker = function(){};
Leaker.prototype = {
init:function(){
this._interval = null;
this.start();
},
start: function(){
var self = this; //递归调用自身
this._interval = setInterval(function(){
self.onInterval();
}, 100);
},
destroy: function(){
if(this._interval !== null){
clearInterval(this._interval);
}
},
onInterval: function(){
count++;
console.log("Interval",count);
}
};
从样的观察
我在按了销毁,leak = null了
可见代码依然还在走,可见此时内存绝对的溢出了,也就是失控了

但是监视器显示该对象回收了

那么这个问题就很明显了,通过leak = null 销毁的只是引用,内部如果还存在引用的话,这个heap是不会被回收的
此时这个内存我们已经管理不到了,会一直递归下去
要解决只能在销毁的时候先停止定时器了
由此可见,引用不仅仅只是外部的, 内部同样存在这样的问题,当然引用类型的机制本来就是这样的
所以在日常的代码编写方面,JS的坑确实不少,接下来看看我项目中的大坑吧!!!
应用截图:


内存使用检测:
Eclipse

Eclipse不熟悉的路过,我们还是回到前端的角度去处理
使用Chrome DevTools的Timeline和Profiles提高Web应用程序的性能
具体的使用就不介绍了,大家接着看
抓怕的heap快照,实时反馈的信息
系统的闭包数

加上JQuery

项目中的

视图解释
列字段解释:
Constructor -- 构造器
Distance -- 估计是对象到根的引用层级距离
Objects Count -- 给出了当前有多少个该类的对象
Shallow Size -- 对象所占内存(不包含内部引用的其它对象所占的内存)(单位:字节)
Retained Size -- 对象所占总内存(包含内部引用的其它对象所占的内存)(单位:字节)
小伙伴都吓呆了
项目中除去系统与一些插件的,至少有上千个闭包
分析堆快照

Object's retaining tree视图显示出了该对象被哪些对象引用了,以及这个引用的名称
关于XUTUTIL.Event类
XUTUTIL.Event是一个构函数函数,主要就是一个订阅/发布模式
那么这个图我的理解就是通过XUTUTIL构造生成的的对象都应该是放到这个里面,所以
根据分析图显示,这个类有208个对象,被实例了208次,也就是说存在这么多订阅者了
XUTUTIL部分源码(观察者模式)

XUTUTIL.Event//=================自定义事件===================
//
// 所有组件的最顶层类
//
// 一个抽象基类
// 为事件机制的管理提供一个公共接口
// 子类应有一个"events"属性来定义所有的事件。
//
//==============================================
(function () { var XUTUTIL = Xut.util,
EACH = Xut.each,
TRUE = true,
FALSE = false; XUTUTIL.Observable = function () {
var me = this, e = me.events;
if (me.listeners) {
me.on(me.listeners);
delete me.listeners;
}
me.events = e || {};
}; /**
* 1 数据保护
* 2 事件冒泡
* 3 事件挂起
* @type {Object}
*/
XUTUTIL.Observable.prototype = {
/**
* 加入一个事件处理函数
* @param {String} 事件处理函数的名称
* @param {Function} 事件处理函数
* @param {Object} (可选的) 事件处理函数执行时所在的作用域。处理函数“this”的上下文
*
* eventName 是对象形式,递归分解
* events 保存所有事件对象
*/
addListener:function (eventName, fn, scope, o) {
var me = this,
e,
oe,
localEvent; //分解元素
if (typeof eventName == 'object') {
o = eventName;
for (e in o) {
oe = o[e];
if (!me.filterOptRe.test(e)) {
me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
}
}
} else {
//创建
eventName = eventName.toLowerCase();
localEvent = me.events[eventName] || TRUE;
//第一次添加,创建Event对象
if (typeof localEvent == 'boolean') {
me.events[eventName] = localEvent = new XUTUTIL.Event(me, eventName);
}
localEvent.addListener(fn, scope, typeof o == 'object' ? o : {});
}
},
……………………………………………………………
};
如图me.events[eventName]标记,是数组保存了观察对象了
我们在翻一页看看,生成对比快照
点击图中的黑色实心圆圈按钮,即可得到第二个内存快照:
点击图中的“Summary”,可弹出一个列表,选择“Comparison”选项,然后选择对比第一个,结果如下图:

这个视图列出了当前视图与上一个视图的对象差异。
列名字段解释:
# New -- 新建了多少个对象
# Deleted -- 回收了多少个对象
# Delta -- 对象变化值,即新建的对象个数减去回收了的对象个数
ALLOC -- 变化的内存大小(字节)注意Delta字段,尤其是值大于0的对象
很明显翻一页就创建大量的观察对象

*注:因为是单页面应用,动态多页面的翻页算法,比如当前是从第2页到第3页,其实是预先创建第4页面,销毁第1页,保留234页,所以这个+14,不是这样算的
但是第一个很明显的问题就出来,为什么要动态创建这么多的观察对象,找到代码来源

找到问题了
注册了大量的观察者模式

销毁的代码,没有处理注销观察者事件

啪啪啪啪。。。。一阵修改之后
翻页的时候不处理了

在进入页面初始化的时候208变成18个了。。。在看看内存占用。。。45016---3240
PC上的消耗,在移动端就会被放大的,所以不要放过过任何一个可优化的地方
修改前

修改后

因为这个案例比较明显,还有的问题,要靠自己慢慢去分析引用情况了
那么很明显了:观察者模式引起的内存泄漏
需要观察者模式(Observer)来解藕一些模块,但如果使用不当,也会带来内存泄漏的问题。
排查这类型的内存泄漏问题,主要重点关注被引用的对象类型是闭包(closure)和数组Array的对象。
1.如果能避免观察模式的使用,就尽量避免,
2.避免不了一定要记得清理
总结出以下几种常见的情况:
1.闭包上下文绑定后没有释放;
2.观察者模式在添加通知后,没有及时清理掉;
3.定时器的处理函数没有及时释放,没有调用clearInterval方法;
4.视图层有些控件重复添加,没有移除。
大型应用,优化是任重道远的,本文只是希望起到一个抛砖引玉的作用。。。。。。。。。。。。。。
各位道友,你们怎么看?如果觉得有收获就点击一下 推荐哇~~~~~
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢!
实战Hybird app:内存溢出与优化的更多相关文章
- win8 app内存溢出检测工具PerfView.exe的使用
PerfView使用教程:https://msdn.microsoft.com/en-us/magazine/jj721593.aspx PerfView下载地址:https://www.micros ...
- 修改Android手机的“虚拟机堆大小”和android:largeHeap来防止APP内存溢出问题
使用“RAM Manager”修改“虚拟机堆大小”为某一个阀值 xxMB大小 修改 AndroidManifest.xml 里的 Application 标签的属性 android:largeHeap ...
- iis应用池内存溢出卡死优化
1.修改回收阀值memoryLimit 在ASP.NET Web服务器上,ASP.NET所能够用到的内存,通常不会等同于所有的内存数量.在machine.config(C:/WINDOWS/Micro ...
- MAT实战:JVM内存溢出的定位与分析
- Android性能优化系列---管理你的app内存
文章出处:http://developer.android.com/training/articles/memory.html#YourApp Random-access memory(RAM)在任 ...
- Android之内存泄露、内存溢出、内存抖动分析
内存 JAVA是在JVM所虚拟出的内存环境中运行的,内存分为三个区:堆.栈和方法区.栈(stack):是简单的数据结构,程序运行时系统自动分配,使用完毕后自动释放.优点:速度快.堆(heap) ...
- Java内存溢出优化性能优化
高性能应用构成了现代网络的支柱.LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求.要优化用户体验,低延迟地响应这些请求非常重要. 比如说,用户经常用到的一个功能是了解动态信息——不断更 ...
- JVM 内存溢出 实战 (史上最全)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
- phpexcel 内存溢出 优化
最近我们公司的项目的在导出excel的时候偶尔出现内存溢出错误,经过测试发现当数据量大于5000条就出现这个问题(默认php.ini memory 是128M) Allowed memory size ...
随机推荐
- POJ 1979 题解
Red and Black Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 31722 Accepted: 17298 D ...
- 关于tableviewcell的一些必备常识
1.设置tableview的背景颜色当设置tableview.backgroundcolor无效时,这样设置: UIView *view = [[UIView alloc] initWithFr ...
- java基础1_标识符,数据类型
JDK的卸载与安装 : 1 卸载 a 从程序中卸载 控制面板 - 程序和功能 - 卸载JDK; b 删除 C:\Windows\System32 下面的 java javac java ...
- Linux 下安装中文 ctex 指南
大家在用 $\LaTeX$ 进行中文排版时相信会遇到不少问题,而$\textbf{ctex}$套装的出现则有效的解决了这一问题,只要安装了$\textbf{ctex}$那么在文中不用进行引用设置就可以 ...
- Python之路Day15--CSS补充以及JavaScript(一)
一.上节作业问题: 上节作业问题: 1.css重用 <style> 如果整个页面的宽度 > 900px时: { .c{ 共有 } .c1{ 独有 } } .c2{ 独有 } < ...
- CF2.C
C. Vladik and fractions time limit per test 1 second memory limit per test 256 megabytes input stand ...
- Oracle EBS - Form DEV Env
1. 创建文件夹resource与forms, 以便存放pll与forms(主要用到APSTAND.fmb, APPSTAND.fmb, TEMPLATE.fmb)文件; 2. 修改注册表 HKEY_ ...
- JQuery之$.ajaxPOST数据
function postSimpleData() { $.ajax({ type: "POST", url: "/Service/SimpleData", c ...
- linux 上安装redis
下载地址:http://redis.io/download,下载最新文档版本. 本教程使用的最新文档版本为 2.8.17,下载并安装: $ wget http://download.redis.io/ ...
- C++ 控制台代码输出控制
在C++控制台应用程序中可以控制控制台输出的字体颜色和 接受任意按键退出 #ifndef CONSOLE_UTILS_H #define CONSOLE_UTILS_H #include <wi ...