三種常見 Memory Leak 循環參照、事件循環監聽、存取全域變數

me
林彥成
2019-10-01 | 2 min.
文章目錄
  1. 1. Memory Leak 原因
    1. 1.1. 循環參照
      1. 1.1.1. Memory Leak 工具推薦
    2. 1.2. 事件循環監聽
    3. 1.3. 存取全域變數
  2. 2. Memory Leak 解決

記憶體流失(Memory Leak)的原因是在程式撰寫上的常見錯誤,當使用無內建垃圾回收機制的語言,更容易因為疏忽造成記憶體無法釋放被再次使用的結果,最終讓程式效能因使用時間拉長而變差。

Memory Leak 原因

記憶體流失 (Memory Leak) 發生的原因,依照程式語言的回收機制可分成兩種:

  1. 沒有回收機制: C 或是 C++ 在存取與 process 獨立的資源後要記得手動釋放
  2. 有回收機制: 即使像 Java 或 JavaScript 這類有回收機制的語言,仍有可能因撰寫疏忽造成記憶體無法被自動回收,常見記憶體流失(Memory leak)的情境如下:
    • 循環參照
    • 事件循環監聽
    • 存取全域變數

循環參照

同時太多地方去存取同個資源,對自動回收機制來說,就無法確定資源什麼時候沒有被使用。舉例生活上的例子來說:

  1. A 需要今天下午抄 B 的答案才能交作業
  2. B 需要今天下午抄 A 的答案才能交作業

Memory Leak 工具推薦

這邊推薦一個方便的套件叫做 madge 可以幫我們產生圖像化的參照圖。

1
2
3
4
5
6
7
const madge = require("madge");

madge("path/to/app.js")
.then((res) => res.image("path/to/image.svg"))
.then((writtenImagePath) => {
console.log("Image written to " + writtenImagePath);
});

紅色就代表有循環參照 (circular dependencies) 產生
CircularDependencies

事件循環監聽

在程式的撰寫上,可能因為疏忽就一直增加監聽。舉例生活上的例子來說:

  1. A 收到問題後,請 B 幫忙查詢問題
  2. B 收到問題後,請 C 幫忙查詢問題
  3. C 收到問題後,請 A 幫忙查詢問題

舉 Socket.IO 的例子來說,主要是以下兩個功能:

  • 監聽訂閱的訊息
  • 針對監聽到的訊息再發送訊息

在這兩個功能互動的過程中,如果需要和 React.js 搭配使用,就要注意 Rerender 時:

  • 是否重複產生新的 socket
  • 是否重複監聽事件

存取全域變數

避免 closure 去存取到全域變數,像是 setInterval 遇上 closure。

1
2
3
4
function foo(arg) {
bar = "this is a hidden global variable";
// window.bar = "this is a hidden global variable";
}

Memory Leak 解決

  1. 使用 delete 和將變數設為 null,手動告訴機器這個物件沒有使用了
1
2
3
4
5
var myVar = "Hello";
var myVar1 = myVar;
myVar = null;
delete myVar;
console.log(myVar1);
  1. 利用開發者工具中的快照,簡單用法就是使用一陣子之後重新抓一次快照,觀察記憶體有沒有上升太多
  2. 事件中的 listener 可以放個 console.log('避免重複監聽') 來暴力觀察

參考連結:


喜歡這篇文章,請幫忙拍拍手喔 🤣

share