实战Hybird app:内存溢出与优化

简介:

主要的问题:

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 应用程序。在那样的单页中,从应用程序的一个状态到另一个状态时,很容易保留不再需要或不相关的内存。

典型的就是: 单页面模拟多页面的行为

 


简单的内存管理测试

视图:

image

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 ,产生一个对象

image

 

点击Destroy,对象销毁

image

那么这个图很形象的说明了,#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了

可见代码依然还在走,可见此时内存绝对的溢出了,也就是失控了

image

 

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

image

那么这个问题就很明显了,通过leak = null 销毁的只是引用,内部如果还存在引用的话,这个heap是不会被回收的

此时这个内存我们已经管理不到了,会一直递归下去

要解决只能在销毁的时候先停止定时器了

 

由此可见,引用不仅仅只是外部的, 内部同样存在这样的问题,当然引用类型的机制本来就是这样的

 

所以在日常的代码编写方面,JS的坑确实不少,接下来看看我项目中的大坑吧!!!


 

应用截图:

9_T`B{QZZKGCVJHU@0E[]S2 image

 

内存使用检测:

Eclipse

image

 

Eclipse不熟悉的路过,我们还是回到前端的角度去处理

使用Chrome DevTools的Timeline和Profiles提高Web应用程序的性能

 

具体的使用就不介绍了,大家接着看

抓怕的heap快照,实时反馈的信息

系统的闭包数

image

 

加上JQuery

image

 

项目中的

image

 

视图解释

列字段解释:

Constructor -- 构造器

Distance -- 估计是对象到根的引用层级距离

Objects Count -- 给出了当前有多少个该类的对象

Shallow Size -- 对象所占内存(不包含内部引用的其它对象所占的内存)(单位:字节)

Retained Size -- 对象所占总内存(包含内部引用的其它对象所占的内存)(单位:字节)

 

小伙伴都吓呆了

项目中除去系统与一些插件的,至少有上千个闭包

 

分析堆快照

image

Object's retaining tree视图显示出了该对象被哪些对象引用了,以及这个引用的名称

 

关于XUTUTIL.Event类

XUTUTIL.Event是一个构函数函数,主要就是一个订阅/发布模式

那么这个图我的理解就是通过XUTUTIL构造生成的的对象都应该是放到这个里面,所以

根据分析图显示,这个类有208个对象,被实例了208次,也就是说存在这么多订阅者了

XUTUTIL部分源码(观察者模式)

XUTUTIL.Event

如图me.events[eventName]标记,是数组保存了观察对象了

 


点击图中的黑色实心圆圈按钮,即可得到第二个内存快照:

点击图中的“Summary”,可弹出一个列表,选择“Comparison”选项,然后选择对比第一个,结果如下图:

image

这个视图列出了当前视图与上一个视图的对象差异。

列名字段解释:

# New -- 新建了多少个对象

# Deleted -- 回收了多少个对象

# Delta -- 对象变化值,即新建的对象个数减去回收了的对象个数

ALLOC -- 变化的内存大小(字节)注意Delta字段,尤其是值大于0的对象

 

很明显翻一页就创建大量的观察对象

image

*注:因为是单页面应用,动态多页面的翻页算法,比如当前是从第2页到第3页,其实是预先创建第4页面,销毁第1页,保留234页,所以这个+14,不是这样算的

 

但是第一个很明显的问题就出来,为什么要动态创建这么多的观察对象,找到代码来源

image

 

找到问题了

注册了大量的观察者模式

image

 

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

image

 

啪啪啪啪。。。。一阵修改之后

翻页的时候不处理了

image

 

在进入页面初始化的时候208变成18个了。。。在看看内存占用。。。45016---3240

PC上的消耗,在移动端就会被放大的,所以不要放过过任何一个可优化的地方

修改前

image

修改后

image

因为这个案例比较明显,还有的问题,要靠自己慢慢去分析引用情况了

 

那么很明显了:观察者模式引起的内存泄漏

需要观察者模式(Observer)来解藕一些模块,但如果使用不当,也会带来内存泄漏的问题。

排查这类型的内存泄漏问题,主要重点关注被引用的对象类型是闭包(closure)和数组Array的对象。

1.如果能避免观察模式的使用,就尽量避免,

2.避免不了一定要记得清理

 


总结出以下几种常见的情况:

1.闭包上下文绑定后没有释放;

2.观察者模式在添加通知后,没有及时清理掉;

3.定时器的处理函数没有及时释放,没有调用clearInterval方法;

4.视图层有些控件重复添加,没有移除。

 

本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/p/3403374.html,如需转载请自行联系原作者

相关文章
|
23天前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
32 2
|
2月前
|
消息中间件 Java
【实战揭秘】如何运用Java发布-订阅模式,打造高效响应式天气预报App?
【8月更文挑战第30天】发布-订阅模式是一种消息通信模型,发送者将消息发布到公共队列,接收者自行订阅并处理。此模式降低了对象间的耦合度,使系统更灵活、可扩展。例如,在天气预报应用中,`WeatherEventPublisher` 类作为发布者收集天气数据并通知订阅者(如 `TemperatureDisplay` 和 `HumidityDisplay`),实现组件间的解耦和动态更新。这种方式适用于事件驱动的应用,提高了系统的扩展性和可维护性。
37 2
|
2月前
|
存储 缓存 JSON
一行代码,我优化掉了1G内存占用
这里一行代码,指的是:String.intern()的调用,为了调用这一行代码,也写了几十行额外的代码。
|
2月前
|
NoSQL Java 测试技术
Golang内存分析工具gctrace和pprof实战
文章详细介绍了Golang的两个内存分析工具gctrace和pprof的使用方法,通过实例分析展示了如何通过gctrace跟踪GC的不同阶段耗时与内存量对比,以及如何使用pprof进行内存分析和调优。
54 0
Golang内存分析工具gctrace和pprof实战
|
2月前
|
机器学习/深度学习 数据采集 PyTorch
构建高效 PyTorch 模型:内存管理和优化技巧
【8月更文第27天】PyTorch 是一个强大的深度学习框架,被广泛用于构建复杂的神经网络模型。然而,在处理大规模数据集或使用高性能 GPU 进行训练时,有效的内存管理对于提升模型训练效率至关重要。本文将探讨如何在 PyTorch 中有效地管理内存,并提供一些优化技巧及代码示例。
48 1
|
2月前
|
Java 容器
【Azure Function App】Java Function在运行中遇见内存不足的错误
【Azure Function App】Java Function在运行中遇见内存不足的错误
|
2月前
|
Linux 测试技术 C++
内存管理优化:内存泄漏检测与预防。
内存管理优化:内存泄漏检测与预防。
44 2
|
21天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
42 0
|
2月前
|
关系型数据库 MySQL
MySQl优化:使用 jemalloc 分配内存
MySQl优化:使用 jemalloc 分配内存
|
2月前
|
Web App开发 移动开发 前端开发
如何优化运行在webkit上的web app
如何优化运行在webkit上的web app
下一篇
无影云桌面