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
如圖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,謝謝!