有那么一句名言躲因,過早優(yōu)化是萬惡之源。指的就是在開發(fā)過程中,不用太早考慮性能問題大脉,而是要優(yōu)先實現(xiàn)功能和保持代碼清晰搁嗓,簡潔。更深的原因箱靴,是對個人的判斷的極端不信任腺逛。我們覺得重要的往往不重要,我們覺得不重要的事后才發(fā)現(xiàn)很重要衡怀。事前的判斷棍矛,往往會錯得離譜。所以抛杨,在軟件工程中够委,就放棄了事前對可能的性能占用情況的評估,而是在實現(xiàn)運行中怖现,通過軟件工具茁帽,來分析性能瓶頸,即:profiling.
我最近就遇到一個profiling的情況屈嗤。
最初是因為有那么一個bug潘拨。導(dǎo)致js后臺處理進程,一小時左右饶号,會自動退出铁追。經(jīng)過分析,我發(fā)現(xiàn)這是我所使用的kafka連接庫產(chǎn)生的問題茫船,這個問題不好修復(fù)琅束,但是我可以使用trick繞過它。處理之后算谈,進程不會報錯退出了涩禀,但是,在運行3-4小時后然眼,進程的處理性能會出現(xiàn)巨大的下降艾船,而且內(nèi)存占用會不停的增長,不停的刷新占用的上限罪治。
很明顯丽声,這個js進程存在內(nèi)存泄漏的情況礁蔗。
但是內(nèi)存泄漏是哪里產(chǎn)生的咧觉义?
由于之前沒有做過profiling,無法判斷是舊有的代碼就有問題,還是新加入的代碼導(dǎo)致的浴井。所以只能全局的去考慮晒骇。
最開始,我使用了nodejs自帶的profiling工具進行過一些cpu性能分析
https://nodejs.org/en/docs/guides/simple-profiling/
后來我又找到了更直觀的node-inspector
https://github.com/node-inspector/node-inspector
通過node-inspector分析,所能了解的也不多洪囤。只是看到closure徒坡,也就是閉包,有占到26%的內(nèi)存瘤缩,是最大的一項喇完。
這么說,是我代碼中的closure導(dǎo)致的內(nèi)存泄漏剥啤。我又搜索了相關(guān)資料锦溪,了解了一下closure導(dǎo)致內(nèi)存泄漏的常見形式。給我啟發(fā)最大的是這么一篇:
http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html
在里面提到j(luò)s v8一個驚人的細節(jié)府怯。在同一作用域定義的閉包刻诊,會共享上下文(context),經(jīng)典的觸發(fā)代碼如下:
var str = new Array(1000000).join('*');
var doSomethingWithStr = function () {
? if (str === 'something')
? ? console.log("str was something");
? };
? doSomethingWithStr();
? var logIt = function () {
? ? console.log('interval');
? }
? setInterval(logIt, 100);
};
setInterval(run, 1000);
在這段代碼中l(wèi)ogIt和doSomethingWithStr共享上下文牺丙,引用了str则涯,導(dǎo)致str不被會gc清理,從而內(nèi)存泄漏冲簿。
我又看了一下代碼粟判,代碼中并沒有使用setInteval函數(shù),但使用的Rxjs峦剔,Rxjs的Observable.subscribe函數(shù)會不會有和setInteval函數(shù)類似的效果浮入。我立即寫了段代碼測試了一下。
var subscription = null
var sequence = null
function run() {
? var str = new Array(1000000).join('*');
? if (subscription != null) {
? ? subscription.dispose()
? }
var sequence = Rx.Observable.interval(200)
var subscription = sequence.subscribe(
? function() {
? ? console.log("str[0]=", str[0])
? })
}
setInterval(run, 500);
使用工具查看羊异,發(fā)現(xiàn)這段代碼會導(dǎo)致內(nèi)存占用迅速上升事秀,即存在內(nèi)存泄漏。
經(jīng)過試驗后野舶,我找到了初步的解決方案易迹。
var subscription = null
var sequence = null
function run() {
? var str = new Array(1000000).join('*');
? if (subscription != null) {
? ? subscription.dispose()
? }
? var sequence = Rx.Observable.interval(200).take(3)
? var subscription = sequence.subscribe(
? function() {
? ? console.log("str[0]=", str[0])
? } , function(err) {
? ? console.log("err=", err)
? } , function(err) {
? ? str=null
? ? console.log("completed")
? } )
}
setInterval(run, 500);
即在消息流結(jié)束的時候,將str對象置為null,來防止str對象的泄漏平道。本以為這就是最佳方案睹欲。無意中我又發(fā)現(xiàn),哪怕沒有在onCompleted函數(shù)一屋,只要流結(jié)束窘疮,也不會內(nèi)存泄漏。
我覺得這個rxjs的一個bug冀墨。在dispose后闸衫,不就是應(yīng)該該把要清理都清理了,是吧诽嘉?