什么是內(nèi)存泄露?
內(nèi)存泄露是指new了一塊內(nèi)存,但無(wú)法被釋放或者被垃圾回收淮逊。new了一個(gè)對(duì)象之后,它申請(qǐng)占用了一塊堆內(nèi)存扶踊,當(dāng)把這個(gè)對(duì)象指針置為null時(shí)或者離開(kāi)作用域?qū)е卤讳N毀泄鹏,那么這塊內(nèi)存沒(méi)有人引用它了在JS里面就會(huì)被自動(dòng)垃圾回收。但是如果這個(gè)對(duì)象指針沒(méi)有被置為null秧耗,且代碼里面沒(méi)辦法再獲取到這個(gè)對(duì)象指針了备籽,就會(huì)導(dǎo)致無(wú)法釋放掉它指向的內(nèi)存,也就是說(shuō)發(fā)生了內(nèi)存泄露分井。
Chrome dev tool 術(shù)語(yǔ)
Shallow size
這是指對(duì)象本身獲得的內(nèi)存大小车猬。
典型的 JavaScript 對(duì)象會(huì)獲得一些保留的內(nèi)存,用于他們的描述以及存儲(chǔ)即時(shí)產(chǎn)生的值尺锚。通常情況下珠闰,只有數(shù)組和字符串才會(huì)有比較明顯的淺層大小。不過(guò)瘫辩,字符串和外部數(shù)組往往在渲染內(nèi)存中有它們自己的主存儲(chǔ)器伏嗜,對(duì) JavaScript 堆只露出一點(diǎn)包裝后的對(duì)象。
渲染內(nèi)存是指所監(jiān)視的頁(yè)面被渲染的過(guò)程中使用的內(nèi)存:原本分配的內(nèi)存 + 該頁(yè)面在 JS 堆中的內(nèi)存 + 所有因?yàn)樵擁?yè)面而導(dǎo)致的 JS 堆中其他對(duì)象的內(nèi)存開(kāi)銷伐厌。然而承绸,即使是一個(gè)小的對(duì)象也可以通過(guò)阻止垃圾回收器自動(dòng)回收其他對(duì)象來(lái)間接保有大量的內(nèi)存。
Retained size
這是指對(duì)象以及其相關(guān)的對(duì)象一起被刪除后所釋放的內(nèi)存大小挣轨,并且 GC roots 無(wú)法到達(dá)該處军熏。
GC roots 是由在從原生代碼的 V8 之外引用 JavaScript 對(duì)象的時(shí)候所創(chuàng)建的句柄(局部或者全局的)構(gòu)成的。這些句柄可以再堆的快照中 GC roots > Handle scope 以及 GC roots > Global handles 中找到刃唐。在沒(méi)有談及瀏覽器實(shí)現(xiàn)的細(xì)節(jié)的情況下,就在本文中說(shuō)明句柄會(huì)令讀者感到困惑界轩,故而關(guān)于句柄的細(xì)節(jié)本文不做講解画饥。事實(shí)上,無(wú)論 GC roots 還是句柄浊猾,都不是你需要擔(dān)心的東西抖甘。
使用 Chrome 任務(wù)管理器實(shí)時(shí)監(jiān)視內(nèi)存使用
使用 Chrome 任務(wù)管理器作為內(nèi)存問(wèn)題調(diào)查的起點(diǎn)。 任務(wù)管理器是一個(gè)實(shí)時(shí)監(jiān)視器葫慎,可以告訴您頁(yè)面當(dāng)前正在使用的內(nèi)存量衔彻。
1薇宠、按 Shift+Esc 或者轉(zhuǎn)到 Chrome 主菜單并選擇 More tools > Task manager,打開(kāi)任務(wù)管理器艰额。
2澄港、右鍵點(diǎn)擊任務(wù)管理器的表格標(biāo)題并啟用 JavaScript memory。
下面兩列可以告訴您與頁(yè)面的內(nèi)存使用有關(guān)的不同信息:
- Memory 列表示原生內(nèi)存柄沮。DOM 節(jié)點(diǎn)存儲(chǔ)在原生內(nèi)存中回梧。 如果此值正在增大,則說(shuō)明正在創(chuàng)建 DOM 節(jié)點(diǎn)祖搓。
- JavaScript Memory 列表示 JS 堆狱意。此列包含兩個(gè)值。 您感興趣的值是實(shí)時(shí)數(shù)字(括號(hào)中的數(shù)字)拯欧。 實(shí)時(shí)數(shù)字表示您的頁(yè)面上的可到達(dá)對(duì)象正在使用的內(nèi)存量详囤。 如果此數(shù)字在增大,要么是正在創(chuàng)建新對(duì)象镐作,要么是現(xiàn)有對(duì)象正在增長(zhǎng)藏姐。
使用堆快照發(fā)現(xiàn)已分離 DOM 樹(shù)的內(nèi)存泄漏
只有頁(yè)面的 DOM 樹(shù)或 JavaScript 代碼不再引用 DOM 節(jié)點(diǎn)時(shí),DOM 節(jié)點(diǎn)才會(huì)被作為垃圾進(jìn)行回收滑肉。 如果某個(gè)節(jié)點(diǎn)已從 DOM 樹(shù)移除包各,但某些 JavaScript 仍然引用它,我們稱此節(jié)點(diǎn)為“已分離”靶庙。已分離的 DOM 節(jié)點(diǎn)是內(nèi)存泄漏的常見(jiàn)原因问畅。此部分將教您如何使用 DevTools 的堆分析器確定已分離的節(jié)點(diǎn)。
堆快照是確定已分離節(jié)點(diǎn)的一種方式六荒。顧名思義护姆,堆快照可以為您顯示拍攝快照時(shí)內(nèi)存在您頁(yè)面的 JS 對(duì)象和 DOM 節(jié)點(diǎn)間的分配。
要?jiǎng)?chuàng)建快照掏击,請(qǐng)打開(kāi) DevTools 并轉(zhuǎn)到 Memory面板卵皂,選擇 Heap Snapshot 單選按鈕,然后按 Take Snapshot 按鈕
快照可能需要一些時(shí)間處理和加載砚亭。完成后灯变,請(qǐng)從左側(cè)面板(名稱為 HEAP SNAPSHOTS)中選擇該快照。
在 Class filter 文本框中鍵入 Detached捅膘,搜索已分離的 DOM 樹(shù)添祸。
展開(kāi)三角符號(hào)以調(diào)查分離的樹(shù)。
使用分配時(shí)間線確定 JS 堆內(nèi)存泄漏
分配時(shí)間線是您可以用于跟蹤 JS 堆中內(nèi)存泄漏的另一種工具寻仗。
要顯示分配時(shí)間線刃泌,請(qǐng)考慮使用下面的代碼
var x = [];
function grow() {
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每次按代碼中引用的按鈕時(shí),都會(huì)向 x
數(shù)組添加一個(gè)由 100 萬(wàn)個(gè)字符組成的字符串。
要記錄分配時(shí)間線耙替,請(qǐng)打開(kāi) DevTools亚侠,然后轉(zhuǎn)到 Memory 面板,選擇 Allocation instrumentation on timeline 單選按鈕俗扇,按 Start 按鈕硝烂,執(zhí)行您懷疑導(dǎo)致內(nèi)存泄漏的操作。完成后狐援,按 stop recording 按鈕 钢坦。
記錄時(shí),請(qǐng)注意分配時(shí)間線上是否顯示任何藍(lán)色豎線(如下面的屏幕截圖所示)啥酱。
這些藍(lán)色豎線表示新內(nèi)存分配爹凹。新內(nèi)存分配中可能存在內(nèi)存泄漏。 您可以在豎線上放大镶殷,將 Constructor 窗格篩選為僅顯示在指定時(shí)間范圍內(nèi)分配的對(duì)象禾酱。
展開(kāi)對(duì)象并點(diǎn)擊它的值,可以在 Object 窗格中查看其更多詳情绘趋。
造成內(nèi)存泄露的可能會(huì)有以下幾種情況:
(1)監(jiān)聽(tīng)在window/body等事件沒(méi)有解綁
(2)綁在EventBus的事件沒(méi)有解綁
(3)Vuex的$store watch了之后沒(méi)有unwatch
(4)模塊形成的閉包內(nèi)部變量使用完后沒(méi)有置成null
(5)使用第三方庫(kù)創(chuàng)建颤陶,沒(méi)有調(diào)用正確的銷毀函數(shù)
(6)被遺忘的計(jì)時(shí)器或回調(diào)函數(shù)
參考鏈接
一個(gè)Vue頁(yè)面的內(nèi)存泄露分析
Chrome開(kāi)發(fā)工具 JavaScript 內(nèi)存分析
chrome內(nèi)存泄露(一)、內(nèi)存泄漏分析工具