## 範例 範例程式如下: ```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)