---
# System prepended metadata

title: Node.js memory leak 案例

---

## 範例
範例程式如下:
```js    
const express = require("express");
const app = express();

const users = new Map();

function newConnect() {
  const user = new User("user_1");
  users.set("user_1", user);
}

function disconnect() {
  users.delete("user_1");
}

class User {
  constructor(username) {
    this.username = username;
    this.bigData1 = "bigDataHello".repeat(1000000);
    this.bigData2 = "bigDataHello,".repeat(1000000).split(",");

    this._startTimer();
  }

  _startTimer() {
    this.timer = setInterval(() => {
      console.log(this.username);
    }, 1000);
  }
}

app.listen(3120, (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("建立 Server: " + `http://localhost:3120`);
  }
});

app.get("/userEnter", (req, res) => {
  newConnect();
  res.send("用戶連線");
});

app.get("/userLeave", (req, res) => {
  disconnect();
  res.send("用戶離線");
});

```

在用戶連線進來之後，會產生一個 User 實例，並且啟動 interval 開始每秒 log username。
用戶離線之後刪除 User 實例在 users 的引用，照理說應該 User 就應該要被 GC 清理掉。

但是經過檢測發現 User 仍然存在 memory 中，並且那些巨大的 data 也依然存在。

- 用戶連線後，產生 User 實例與 bigData
![image](https://hackmd.io/_uploads/ryaPZWELp.png)

- 用戶離線後，User 實例與 bigData 還在
![image](https://hackmd.io/_uploads/BkJXz-NUp.png)


原因是由於 setInterval 會不斷試圖存取該 User 中的 username，導致 User 實例無法被清除，連同 User 中的其他屬性也一併無法清除掉，即使他們並沒有被使用。

可參考:
[Forgotten Timers](https://betterstack.com/community/guides/scaling-nodejs/high-performance-nodejs/nodejs-memory-leaks/#causes-of-memory-leaks)
[Closures](https://betterstack.com/community/guides/scaling-nodejs/high-performance-nodejs/nodejs-memory-leaks/#2-closures)

修改後的程式:
```js    
// ...以上省略
function disconnect() {
    const user = users.get("user_1");
    
    /* 離線後要記得清除 Timer */
    user.clearTimer(); 

  users.delete("user_1");
}

class User {
  constructor(username) {
    this.username = username;
    this.bigData1 = "bigDataHello".repeat(1000000);
    this.bigData2 = "bigDataHello,".repeat(1000000).split(",");

    this._startTimer();
  }

  _startTimer() {
    this.timer = setInterval(() => {
      console.log(this.username);
    }, 1000);
  }

  clearTimer() {
    clearInterval(this.timer);
  }
}
```

修改後再測試一次:

- 用戶連線後，產生 User 實例與 bigData
![image](https://hackmd.io/_uploads/rkgzEWVLa.png)


- 用戶離線後，User 被確實清除掉
![image](https://hackmd.io/_uploads/BJVBV-V8T.png)




## 實際案例

在 TV 中台程式裡面，有一個處理用戶報價連線的 class，其中使用了一個 Timer 來打 HeartBeat，但是用戶離線後並沒有清除這個 Timer，導致大量的相關依賴物件無法被GC清除
- HeartBeat Timer
![image](https://hackmd.io/_uploads/r1SS_bE8p.png)


### 檢測:
- 連線前:
![image](https://hackmd.io/_uploads/Hkk9Pb4Ip.png)

- 5 用戶連線中:
![image](https://hackmd.io/_uploads/H19CPb48T.png)

- 5 用戶離線:
![image](https://hackmd.io/_uploads/HJscvWNLa.png)

---

一樣在離線後加上 clearInterval，就能正常清出使用過的物件:
![image](https://hackmd.io/_uploads/SJ3lPWV8T.png)

### 檢測:

- 連線前:
![image](https://hackmd.io/_uploads/Bks4P-VUT.png)

- 5 用戶連線中:
![image](https://hackmd.io/_uploads/Syj8w-4IT.png)

- 5 用戶離線:
![image](https://hackmd.io/_uploads/rJnvv-VIa.png)
