परिचय: "तोड़ना — बनाना नहीं है"
\nहर Java डेवलपर ने कभी न कभी अपने production एप्लिकेशन में मेमोरी लीक (memory leak) को कोसा है। एप्लिकेशन OutOfMemoryError के साथ क्रैश हो जाता है, सर्वर रीबूट हो जाता है, उपयोगकर्ता गुस्से वाले ईमेल लिखते हैं। लेकिन इस सिक्के का दूसरा पहलू भी है: जावा में मेमोरी लीक कैसे बनाएं जानबूझकर? यह पागलपन लगता है, लेकिन यही सवाल (Stack Overflow से मूल) हजारों व्यूज एकत्र कर चुका है।
यह क्यों जानना चाहिए? ताकि गार्बेज कलेक्टर (GC) की यांत्रिकी और उन एंटी-पैटर्न को समझ सकें जो आपके कोड को छलनी बना देते हैं। 2025 में, जब Java 21 वर्चुअल थ्रेड्स और Loom के साथ पहले से ही मानक है, पुरानी लीक कहीं नहीं गई हैं - वे बस नई सुविधाओं के रूप में छिप जाती हैं।
\nइस लेख में हम न केवल यह दिखाएंगे कि Java एप्लिकेशन को कैसे "खराब" किया जाए, बल्कि 2025 में लीक का पता लगाने के सर्वोत्तम अभ्यास भी देंगे। तैयार हैं? चलिए शुरू करते हैं!
\n\nथोड़ा सिद्धांत: Java "शाश्वत" क्यों नहीं है
\nकई नौसिखिए पवित्र रूप से मानते हैं कि गार्बेज कलेक्टर (GC) स्वचालित रूप से वह सब कुछ हटा देता है जिसकी आवश्यकता नहीं है। वास्तविकता क्रूर है: जावा में मेमोरी लीक संभव है यदि कोई ऑब्जेक्ट रूट रेफरेंस (GC roots) से पहुंच योग्य (reachable) रहता है, लेकिन एप्लिकेशन को इसकी अब आवश्यकता नहीं है।
\n2025 में, Value Objects (Project Valhalla) और बेहतर ZGC (पॉज़ टाइम < 1 ms वाला कलेक्टर) के आगमन के साथ, प्रदर्शन की स्थिति में सुधार हुआ है, लेकिन लीक का तर्क वही रहा। GC कोई टेलीपैथ नहीं है - वह नहीं जानता कि आप HashMap में रेफरेंस को शून्य करना भूल गए हैं।
तरीका #1: क्लासिक — "भूलने वाला" HashMap
\nसबसे लोकप्रिय एंटी-पैटर्न, जो एक लेख से दूसरे लेख में घूमता रहता है। लेकिन इसके बिना काम नहीं चलता।
\nलीक कैसे काम करती है
\nआप ऑब्जेक्ट्स को HashMap (या HashSet) में कुंजी के रूप में डालते हैं, लेकिन उन्हें हटाना भूल जाते हैं। यदि कुंजियाँ mutable ऑब्जेक्ट हैं जो डालने के बाद हैश-कोड बदलती हैं, तो आपको निश्चित रूप से जावा मेमोरी लीक मिलेगी।
import java.util.HashMap;\nimport java.util.Map;\n\npublic class LeakyMapExample {\n public static void main(String[] args) {\n Map<StringBuilder, String> map = new HashMap<>();\n\n for (int i = 0; i < 100_000; i++) {\n StringBuilder key = new StringBuilder("key_" + i);\n map.put(key, "value_" + i);\n // "भूल गए" हटाना — कुंजियाँ हमेशा के लिए लटकी रहती हैं\n }\n System.out.println("मैप का आकार: " + map.size());\n // लीक: हीप में 100_000 StringBuilder + String ऑब्जेक्ट\n }\n}\n\n\nयह लीक क्यों है? जब तक map का रेफरेंस जीवित है, सभी कुंजियाँ और मान हीप में बने रहते हैं। भले ही एप्लिकेशन का तर्क अब इस डेटा का उपयोग न करे, GC इसे नहीं छुएगा।
2025 की सर्वोत्तम प्रथा
\n- \n
- यदि कुंजियाँ अस्थायी ऑब्जेक्ट हैं तो
WeakHashMapका उपयोग करें। \n - कैशिंग के लिए — आकार/समय सीमा वाले
CaffeineयाGuava Cacheका उपयोग करें। \n - कभी भी
StringBuilderको कुंजी के रूप में उपयोग न करें — केवल immutable प्रकारों का उपयोग करें। \n
तरीका #2: स्टैटिक कलेक्शन — मूक हत्यारा
\nस्टैटिक फ़ील्ड JVM के अंत तक जीवित रहते हैं। यदि आप डेटा को static List या static Map में डालते हैं और उन्हें साफ नहीं करते हैं, तो आपको क्लास स्तर पर जावा मेमोरी लीक मिलती है।
import java.util.ArrayList;\nimport java.util.List;\n\npublic class StaticLeak {\n private static final List<byte[]> CACHE = new ArrayList<>();\n\n public static void addData() {\n // प्रत्येक कॉल स्टैटिक सूची में 10 MB जोड़ता है\n CACHE.add(new byte[10 * 1024 * 1024]);\n }\n\n public static void main(String[] args) throws Exception {\n for (int i = 0; i < 1_000; i++) {\n addData();\n Thread.sleep(100);\n }\n // OutOfMemoryError अपरिहार्य है\n }\n}\n\n\n<