DanLevy.net

WeakMap से मेमोरी लीक रोकें

कमज़ोर कोड को कमज़ोर रेफ़रेंसेज़ से ठीक करें!

आपको वह एहसास याद है जब आप कोड की एक लाइन बदलते ही मेमोरी उपयोग में 50 % की गिरावट देखते हैं? मैं वही पल देख रहा था Chrome के DevTools Performance Monitor में, जब एक डैशबोर्ड ऐप एक घंटे में 100 MB मेमोरी खो रहा था और फिर पूरे दोपहर तक साफ‑सुथरा चल रहा था।

एक‑लाइन का बदलाव: new Map() को new WeakMap() कर दिया।

बस इतना ही। वही API सतह, वही उपयोग पैटर्न, लेकिन अंदर‑बाहर पूरी तरह अलग व्यवहार। लेकिन यह समझना कि यह क्यों काम करता है, इसका मतलब है वह बात समझना जो अधिकांश JavaScript डेवलपर्स कभी नहीं सोचते: जब आपके डेटा को अब कोई नहीं देख रहा होता तो क्या होता है।

जब रेफ़रेंसेज़ एंकर बन जाती हैं

JavaScript में एक सामान्य Map अपनी कुंजियों को “कीमती माल” की तरह मानता है। एक बार आप उसमें कुछ डाल देते हैं, तो Map उसे लोहे की पकड़ से पकड़ लेता है। गार्बेज कलेक्टर इस संबंध को देखता है और सोचता है, “स्पष्ट है कि उन्हें अभी भी इस ऑब्जेक्ट की ज़रूरत है, इसे मत छूओ।”

यह सुरक्षा प्रवृत्ति तब समस्या बन जाती है जब आप अस्थायी चीज़ों के बारे में मेटाडेटा स्टोर कर रहे हों। हटाए गए DOM नोड्स। समाप्त होने वाले यूज़र सत्र। अनमाउंट होने वाले कंपोनेंट इंस्टेंस। Map को नहीं पता कि ये ऑब्जेक्ट अब उपयोगी नहीं रहे। उसे सिर्फ़ एक रेफ़रेंस मिला है, इसलिए वह उन्हें जीवित रखता है।

const cache = new Map();
function trackClick(element) {
cache.set(element, { clicks: 0 });
}
document.body.removeChild(element);
// The element is gone from the DOM, but cache is keeping it in memory

Garbage Collector element को साफ़ नहीं कर पाता क्योंकि cache अभी भी उस पर इशारा कर रहा है। इसे “strong reference” कहा जाता है, और लंबे‑चलने वाले Single Page Apps में यह लीक्स बन जाता है जो अंततः ब्राउज़र को क्रैश कर देता है।

WeakMap नियम बदलता है

WeakMap अलग तरह से काम करता है। यह अपनी कुंजियों को स्थायी निवासी की बजाय अस्थायी नागरिक मानता है। जब आप WeakMap में कुछ रखते हैं, तो मूल रूप से आप कह रहे होते हैं: “मैं इस डेटा को इस ऑब्जेक्ट से जोड़ना चाहता हूँ, लेकिन मैं इसका कारण नहीं बनना चाहता कि वह जीवित रहे।”

यदि मेमोरी में किसी ऑब्जेक्ट को रखने का एकमात्र कारण WeakMap है, तो Garbage Collector उसे ले सकता है। जब ऑब्जेक्ट गायब हो जाता है, तो WeakMap एंट्री भी साथ में हट जाती है। मैन्युअल क्लीन‑अप की ज़रूरत नहीं।

const cache = new WeakMap();
function trackClick(element) {
cache.set(element, { clicks: 0 });
}
document.body.removeChild(element);
// The element gets Garbage Collected
// The cache entry vanishes automatically

मैंने 100,000 DOM नोड्स बनाकर, प्रत्येक पर मेटाडेटा संग्रहीत करके, फिर सभी को हटाते हुए एक बेंचमार्क चलाया। Map के साथ, ब्राउज़र ने 150‑200 MB मेमोरी रखी। WeakMap के साथ, यह 70‑80 MB तक गिर गया। वही कोड, वही कार्यक्षमता, आधा मेमोरी फुटप्रिंट।

आप क्या छोड़ते हैं

WeakMap में कुछ प्रतिबंध हैं जो पहली नज़र में सीमाएँ लगती हैं, लेकिन जब आप समझते हैं कि यही जादू को संभव बनाता है, तो ये सीमाएँ समझ में आती हैं।

आप WeakMap पर इटररेट नहीं कर सकते। कोई forEach नहीं, कोई keys() नहीं, कोई values() नहीं। यह तब समझ में आता है जब आप सोचते हैं: गार्बेज कलेक्टर आपके लूप के बीच में ही किसी एंट्री को हटा सकता है। क्या आप वास्तव में ऐसे स्थिति को संभालना चाहते हैं?

आप आकार (size) नहीं देख सकते। कोई .size प्रॉपर्टी नहीं, कोई .length नहीं। फिर से, यह एक चलती हुई लक्ष्य है। जब आप पूछते हैं और जब उत्तर मिलता है, तब तक संख्या बदल सकती है।

कीज़ ऑब्जेक्ट ही होनी चाहिए। स्ट्रिंग्स, नंबर, प्रिमिटिव्स नहीं। यह कमजोर रेफ़रेंसेज़ के काम करने के मूल सिद्धांत से जुड़ा है: प्रिमिटिव मानों की कोई अलग पहचान नहीं होती जिसे उनके मान से अलग ट्रैक किया जा सके।

ये बग नहीं हैं। ये डिज़ाइन का हिस्सा हैं। WeakMap का निर्माण एक ही विशिष्ट काम के लिए किया गया है: ऑब्जेक्ट्स से मेटाडेटा जोड़ना, बिना उन ऑब्जेक्ट्स को साफ़ होने से रोकने के। यदि आपको इटरशन, प्रिमिटिव कीज़ या एंट्रीज़ की गिनती चाहिए, तो आप संभवतः किसी अलग समस्या को हल कर रहे हैं और सामान्य Map का उपयोग करना चाहिए।

जहाँ यह वास्तव में मदद करता है

“प्राइवेट डेटा” पैटर्न WeakMap का मूल उपयोग मामला था, जब जावास्क्रिप्ट में #private फ़ील्ड्स नहीं थे। लाइब्रेरीज़ क्लास के बाहर एक WeakMap बनाती थीं और उसे उन डेटा को स्टोर करने के लिए उपयोग करती थीं जो इंस्टेंस पर एक्सेस नहीं होना चाहिए।

const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}

जब कोई User इंस्टेंस गार्बेज कलेक्ट हो जाता है, तो उसका प्राइवेट डेटा भी साथ में हट जाता है। कोई क्लीन‑अप कोड लिखने की जरूरत नहीं।

Memoization भी यहाँ स्वाभाविक रूप से फिट बैठता है, ख़ासकर जब आप ऑब्जेक्ट इनपुट के आधार पर परिणाम कैश कर रहे हों, न कि प्रिमिटिव वैल्यूज़ पर। यदि आपका महँगा गणना फ़ंक्शन एक कॉन्फ़िग ऑब्जेक्ट को इनपुट लेता है, तो WeakMap का उपयोग करने से आपको कैश को कॉन्फ़िग्स से अधिक समय तक जीवित रखने की चिंता नहीं रहती।

const cache = new WeakMap();
function expensiveCalc(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyMath(obj);
cache.set(obj, result);
return result;
}

कैश केवल उन ऑब्जेक्ट्स के जीवनकाल तक रहता है जो कैश किए जा रहे हैं। एक बार obj कहीं और रेफ़रेंस नहीं रहता, तो कैश्ड परिणाम और कैश एंट्री दोनों एक साथ गायब हो जाते हैं।

कब इसे अपनाएँ

आधुनिक वेब ऐप्स में मेमोरी लीक अक्सर उन पुरानी रेफ़रेंसेज़ से आती हैं जिन्हें साफ़ किया जाना चाहिए था। यदि आप कोई दीर्घकालिक एप्लिकेशन बना रहे हैं—जैसे कि पूरा दिन खुला रहने वाला डैशबोर्ड, कई घंटे चलने वाला चैट ऐप, या कभी रिफ़्रेश न होने वाला एडमिन पैनल—तो आपको यह सोचना पड़ेगा कि पुराने डेटा के साथ क्या होता है।

WeakMap विशेष रूप से तब उपयोगी होता है जब आप डेटा को DOM नोड्स, कंपोनेंट इंस्टेंस, या किसी भी ऑब्जेक्ट के साथ जोड़ रहे हों जिसकी लाइफ़टाइम आप नियंत्रित नहीं कर सकते। यदि आप किसी रेफ़रेंस के आधार पर कुछ स्टोर कर रहे हैं और वह रेफ़रेंस हट सकता है, तो WeakMap सफ़ाई को बहुत आसान बना देता है।

Regular Map अभी भी सही विकल्प है जब आप वास्तविक कैश बना रहे हों जिसमें इविक्शन पॉलिसी हो, जब आपको एंट्रीज़ पर इटररेट करना हो, जब आप प्रिमिटिव कीज़ का उपयोग कर रहे हों, या जब डेटा स्वयं मायने रखता हो न कि उसकी ऑब्जेक्ट के साथ एसोसिएशन।

WeakMap की अच्छी बात यह है कि अक्सर यह स्पष्ट हो जाता है कि आपको इसकी जरूरत है। यदि आप खुद को ऑब्जेक्ट्स नष्ट होने पर मैप एंट्रीज़ हटाने के लिए क्लीन‑अप कोड लिखते हुए पाते हैं, तो यह एक संकेत है। यदि आप मेमोरी के अनियंत्रित रूप से बढ़ने को लेकर चिंतित हैं क्योंकि आपको नहीं पता कब चीज़ें डिलीट करनी हैं, तो यह भी एक संकेत है।

कभी‑कभी सबसे अच्छा फीचर वह होता है जो बिना सोचे‑समझे काम करता रहता है।

Resources