Q50. What are common causes of memory leaks in JavaScript?
ঘন ঘন ঘটা পাঁচটি সমস্যা — সবচেয়ে বেশি কারণ, উপসর্গ ও সমাধান।
Memory leak মানে কোড চলা শেষ হলেও কিছু মেমোরি আর মুক্ত হচ্ছে না। কারণ হলো — কোথাও একটি reference ভুলে থেকে গেছে, তাই GC সেটিকে আর দরকার মনে করে না। লক্ষণ হলো পেজ ব্যবহার করতে থাকলে memory ধীরে ধীরে বেড়ে যায়।
প্রথম অভিজ্ঞতার ধাপে প্রায় সব leak ঘটে একটাই কারণে — অপ্রয়োজনীয় reference বা listener সরিয়ে ফেলা হয় না।
// ❌ Leak: কোনো সময় থাকে, DOM থেকে সরে যায়
function attach(element) {
element.addEventListener("click", () => {
console.log("click হয়েছে");
});
}
// ✅ ঠিক করতে হলে remove করতে হবে
function attach(element) {
function onClick() {
console.log("click হয়েছে");
}
element.addEventListener("click", onClick);
return () => element.removeEventListener("click", onClick);
}element DOM থেকে সরে গেলেও তার উপর থাকা listener-এর reference বেঁচে থাকে, ফলে element মেমোরিতে আটকে থাকে। AbortController ব্যবহার করলে একসাথে সব listener সরানো যায়।
// ❌ Leak: সব interval একসাথে বন্ধ হয় না
for (const id of ids) {
setInterval(() => {
updateWidget(id);
}, 1000);
}
// ✅ সবসময় বন্ধ করা হচ্ছে
const timers = [];
for (const id of ids) {
const timer = setInterval(() => updateWidget(id), 1000);
timers.push(timer);
}
// পরে
timers.forEach(clearInterval);// ❌ Leak: শুধু lastId-তে মান থাকবে, বাকি id বাঁচে যায়
const requestCache = {};
function load(id) {
if (requestCache[id]) return requestCache[id];
const data = fetchData(id);
requestCache[id] = data;
return data;
}
// ✅ সীমিত সাইজের cache
class LRUCache {
constructor(limit) {
this.limit = limit;
this.map = new Map();
}
get(id) {
if (!this.map.has(id)) return undefined;
const value = this.map.get(id);
this.map.delete(id);
this.map.set(id, value); // সাম্প্রতিক ব্যবহার শেষে
return value;
}
set(id, value) {
if (this.map.has(id)) this.map.delete(id);
this.map.set(id, value);
if (this.map.size > this.limit) {
const oldest = this.map.keys().next().value;
this.map.delete(oldest); // সবচেয়ে পুরোনোটা বাদ
}
}
}এখানে প্রতিটি আলাদা id-এর data সংরক্ষিত থাকে, তাই ব্যবহারকারীরা যত বেশি আলাদা জিনিস খুলবে, মেমোরি তত বাড়বে।
// ❌ Leak: reference রেখে দিলে DOM মুক্ত হবে না
const items = document.querySelectorAll(".item");
const observer = new MutationObserver(() => {
const count = items.length;
console.log(count);
});
observer.observe(document.body, { childList: true });
// items এখনও document.body-কে reference করছে, পুরো tree আটকে যায়
// ✅ শুধু দরকারি অংশ রাখুন
const count = document.querySelectorAll(".item").length;// ❌ Leak: closure-এ বড় ডেটা ধরে রাখা
function createCounter() {
const hugeData = new Array(1000000).fill("data");
return () => {
// শুধু counter দরকার, কিন্তু hugeData ও ধরে রাখা হচ্ছে
return hugeData.length;
};
}Map ব্যবহার করলে set দিয়ে সংরক্ষিত মান আর সেই Map যতক্ষণ বেঁচে থাকে, GC সেগুলো মুক্ত করতে পারে না — কারণ Map strong reference ধরে রাখে। WeakMap ব্যবহার করলে key আর reference দুটোই মুক্ত হতে পারে।
// ❌ মুক্ত হবে না
const map = new Map();
const key = { id: 1 };
map.set(key, "tahshil");
// ✅ মুক্ত হতে পারে
const weakMap = new WeakMap();
weakMap.set(key, "tahshil");দ্রুত চেক করার উপায় হলো Chrome DevTools-এর Memory tab। কিছু কাজ করার পরে Heap snapshot নিয়ে দেখুন — শ্রেণি বারবার ঘুরলে সেটিই সম্ভাব্য leak।
| কারণ | উপসর্গ | সমাধান |
|---|---|---|
| Event listener সরানো না | element মেমোরিতে আটকে | removeEventListener বা AbortController |
setInterval বন্ধ না করা | callback সবসময় সচিগ | clearInterval |
| অসীম cache | data জমতে থাকে | সীমিত cache বা WeakMap |
| DOM reference আটকানো | পুরো tree বাঁচে | নতুন DOM query করে রাখুন |
| বড় closure | অপ্রয়োজনীয় ডেটা বাঁচে | ক্ষুদ্র রাখুন, null করে ছাড়ুন |
বেশিরভাগ leak একবারের ছোট ভুলের ফল, কোনো জটিল কারণের নয়। তাই নতুন listener বা timer যোগ করার সময় সাথে সাথে সরানোর বিকল্পও লিখে রাখুন, আর দীর্ঘসময় খোলা থাকা SPA-তে DevTools দিয়ে মাঝে মাঝে heap দেখে নিন।