记忆体管理 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);

参考资料