JavaScript中的垃圾回收和內存泄漏

以前接觸的js的內存管理方面的內容一直比較零散,最近在這一塊作了一些系統的學習.學習過程當中的一些總結在這裏分享給你們.歡迎批評指正,共同窗習,共同進步.javascript

在一部分語言中是提供了內存管理的接口的,例如C語言中的 malloc()free(); 而在 JavaScript 中會自動進行內存的分配和回收的,由於自動這兩個字,就讓不少的開發者認爲咱們是不須要去關心內存方面的問題,固然,這是一種錯誤的見解.關注內存的管理,避免內存的泄漏也是性能優化重要的一項.前端

變量的生命週期

Javascript 變量的生命週期要分開來看,對於全局變量,他的生命週期會持續到頁面關閉(這就涉及到了後面要總結的內存泄漏的一種方式).而對於局部變量,在所在的函數的代碼執行以後,局部變量的生命週期結束,他所佔用的內存會經過垃圾回收機制釋放(即垃圾回收).java

垃圾回收機制

垃圾回收一般有兩種方式來實現:node

引用計數

這種算法在MDN文檔中被稱爲最"天真"的垃圾回收算法;核心原理是: 判斷一個對象是否要被回收就是要看是否還有引用指向它,若是是"零引用",那麼就回收.說這種算法天真,是由於它存在着較爲嚴重的缺陷---循環引用:git

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

複製代碼

首先要注意咱們是在函數做用域中討論的這個問題,而不是全局環境中.老版本的IE中的非JavaScript原生對象如 DOMBOM 對象就採用的這種策略.下面這種狀況下就會出現內存泄漏:github

var el =document.getElementById("some_element");
var Obj =new Object();
myObj.el = el;
el.someObject = Obj;

複製代碼

固然咱們能夠在不用的時候手動釋放:算法

myObj.el = null;
el.someObject = null;

複製代碼

標記清除

這個算法把「對象是否再也不須要」簡化定義爲「對象是否能夠得到」.chrome

這個算法假定有一個根(root)的對象;在 Javascript 裏,根是全局對象,對應於瀏覽器環境的 window,node 環境的 global.垃圾回收器將按期從根開始,找全部從根開始引用的對象,而後找這些對象引用的對象……從根開始,垃圾回收器將找到全部能夠得到的對象和收集全部不能得到的對象.瀏覽器

這個算法相對於引用計數的優點在於,「有零引用的對象」老是不可得到的,可是相反卻不必定,參考「循環引用」.性能優化

從2012年起,全部現代瀏覽器都使用了標記-清除垃圾回收算法,都是在此基礎上進行優化.全部對JavaScript垃圾回收算法的改進都是基於標記-清除算法的改進,並無改進標記-清除算法自己和它對「對象是否再也不須要」的簡化定義.

限制: 那些沒法從根對象查詢到的對象都將被清除 固然,在咱們的開發實踐中不多遇到這種狀況,這也是咱們忽略內存管理的緣由之一.

常見的內存泄漏舉例

1.忘記聲明的局部變量

function a(){
    b=2
    console.log('b沒有被聲明!')
}
複製代碼

b 沒被聲明,會變成一個全局變量,在頁面關閉以前不會被釋放.使用嚴格模式能夠避免.

2.閉包帶來的內存泄漏

var leaks = (function(){
    var leak = 'xxxxxx';// 閉包中引用,不會被回收
    return function(){
        console.log(leak);
    }
})()

複製代碼

固然有時候咱們是故意讓這個變量保存在內存中的,可是要避免無心的時候形成的內存泄漏.

3.移除 DOM 節點時候忘記移除暫存的值

有時候出於優化性能的目的,咱們會用一個變量暫存 節點,接下來使用的時候就不用再從 DOM 中去獲取.可是在移除 DOM 節點的時候卻忘記了解除暫存的變量對 DOM 節點的引用,也會形成內存泄漏

var element = {
  image: document.getElementById('image'),
  button: document.getElementById('button')
};

document.body.removeChild(document.getElementById('image'));
// 若是element沒有被回收,這裏移除了 image 節點也是沒用的,image 節點依然留存在內存中.

複製代碼

與此相似情景還有: DOM 節點綁定了事件, 可是在移除的時候沒有解除事件綁定,那麼僅僅移除 DOM 節點也是沒用的

4. 定時器中的內存泄漏

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
複製代碼

若是沒有清除定時器,那麼 someResource 就不會被釋放,若是恰好它又佔用了較大內存,就會引起性能問題. 再提一下 setTimeout ,它計時結束後它的回調裏面引用的對象佔用的內存是能夠被回收的. 固然有些場景 setTimeout 的計時可能很長, 這樣的狀況下也是須要歸入考慮的.

chrome中查看

老版本的在 Timeline 中查看, 新版本的在 performance 中查看:

image

步驟:

  1. 打開開發者工具 Performance
  2. 勾選 Screenshotsmemory
  3. 左上角小圓點開始錄製(record)
  4. 中止錄製

圖中 Heap 對應的部分就能夠看到內存在週期性的回落也能夠看到垃圾回收的週期,若是垃圾回收以後的最低值(咱們稱爲min),min在不斷上漲,那麼確定是有較爲嚴重的內存泄漏問題.

關於工具的使用暫時在這裏淺嘗輒止了,後面再深刻的學習了開發者工具方方面面的使用再來和你們分享.

參考文檔:

  1. MDN文檔
  2. 推薦給你們的一個ppt

廣而告之

本文發佈於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。

歡迎討論,點個贊再走吧 。◕‿◕。 ~

相關文章
相關標籤/搜索