記憶體管理 Memory Management

Node.js 記憶體管理 Memory Management

低階語言 (e.g. C, C++) 需要開發者自己決定,當程式執行到某個地方時,是否有已被分配的記憶體不再需要。並手動將其釋放。

高階的語言 (e.g. JavaScript) 有一個叫作 垃圾回收器(garbage collector) 的系統,他的工作是追蹤記憶體分配的使用情況,以便自動釋放一些不再使用的記憶體空間。

但這個垃圾回收器只是「儘量」做到自動釋放記憶體空間,因為判斷記憶體空間是否要繼續使用,這件事是「不可判定(undecidable)」的(不能用演算法來解決)。

Garbage collection 垃圾回收機制流程

瀏覽器偵測到某個物件不在被使用時,會執行垃圾回收(garbage collection)的機制,以此釋放記憶體空間。

  • 定期從根物件 (root,在瀏覽器中是 window,node 則是 global) 開始往下探詢每一個子節點
  • 並清除沒有被探詢到、或是沒有被探詢物件參考的物件,也就是所謂「無法到達的物件 (unreachable objects)」

Memory Leak

如果 root -> F 的參考消失,導致 F 變成「無法到達的物件」,那麼 F 與其子節點們就會被自動回收。

Stack & Heap

  • 比較簡單類型的 Primitive Type 會被放在 stack 裡
  • 比較複雜類型的 Reference Type 則會把資料存在 heap 中,再把資料在 heap 的記憶體位址記錄到 stack 裡

Memory Leak

可以看到 Object 類型的數據實際上是存在 Heap 裡,Stack 中存的只是物件在 Heap 中的記憶體位置而已

變數 four = three 這段 code 實際上是把 Three 指向的物件在 Heap 中的記憶體位置 指派給 Four 變數,所以它們實際上指向的是同一個物件

Stack & Heap Garbage collection 回收機制

如果是物件的話,Stack 中存的是 Heap 空間的 address

所以就算 Stack 被回收,存在 Heap 空間的數據依然存在,這時就需要靠 GC 來判斷 Heap 空間中哪些資料是用不到且需要被回收的

刪除變數釋放記憶體

使用 delete 刪除物件屬性

const Employee = {
    name: 'Kay',
    age: 17
};

// Kay
console.log(Employee.name);

// 刪除物件屬性值
delete Employee.name;

// undefined
console.log(Employee.name);
// { age: 17 }
console.log(Employee);

將變數設為 null

var myVar = "Hello";
// Hello
console.log(myVar);
myVar = null;
// null
console.log(myVar);

clearInterval() 清除無用的 Timer

function setCallback() {
    // 拆解變數將 counter 獨立
    let counter = 0;
    // 在 setCallback 回傳 return 資料後會被移除
    const hugeString = new Array(100000).join('x');

    return function cb() {
        // 只有 counter 是 callback 的 scope 變數
        counter++;
        console.log(counter);
    }
}
// 儲存 Interval Timer ID
const timerId = setInterval(setCallback(), 1000); // saving the interval ID
// 清除 Timer
clearInterval(timerId);

移除元素前先移除 Event Listener

避免元素還有其他變數在使用,所以移除前,相關的綁定使用的變數要先移除

  • 移除 Event Listener
  • 移除元素
var element = document.getElementById('button');

function onClick(event) {
    element.innerHtml = 'text';
}

element.addEventListener('click', onClick);
// 移除 Event Listener
element.removeEventListener('click', onClick);
// 移除元素
element.parentNode.removeChild(element);

參考資料