मेमोरी लीक (Memory Leak) वह स्थिति है जब एप्लिकेशन उन ऑब्जेक्ट्स के संदर्भ (references) को रोके रखता है जिनकी अब आवश्यकता नहीं है, जिससे गार्बेज कलेक्टर (GC) व्याप्त मेमोरी को मुक्त नहीं कर पाता। LeakCanary के अनुसार, अच्छी तरह से लिखे गए एप्लिकेशन में भी प्रति 10,000 लाइन कोड में 3–5 लीक पाए जाते हैं। प्रत्येक लीक धीरे-धीरे उपलब्ध मेमोरी को कम करता है, जिससे सुस्ती और OutOfMemoryError होता है।
मुख्य बिंदु
मेमोरी लीक (Memory Leak) वह स्थिति है जहां एक ऑब्जेक्ट स्ट्रांग रेफरेंस (Strong Reference) की श्रृंखला के माध्यम से पहुंच योग्य रहता है, भले ही वह तार्किक रूप से एप्लिकेशन के लिए आवश्यक न रहा हो। गार्बेज कलेक्टर (GC) ऐसे ऑब्जेक्ट को जीवित मानता है और उसके द्वारा घेरी गई मेमोरी को मुक्त नहीं करता। परिणामस्वरूप, उपलब्ध हीप मेमोरी लगातार घटती है और GC ठहराव की आवृत्ति बढ़ती है।
इसके विपरीत मैन्युअल मेमोरी प्रबंधन वाली भाषाओं (C, C++) में, Java/Kotlin में लीक भूला हुआ free() नहीं है, बल्कि भूला हुआ संदर्भ है। जब तक GC रूट से लीक हुए ऑब्जेक्ट तक एक स्ट्रांग रेफरेंस मौजूद है, GC इसे आवश्यक मानता है। विशिष्ट GC रूट: स्थिर फ़ील्ड, सक्रिय थ्रेड, कॉल स्टैक, JNI वैश्विक संदर्भ।
लीक का खतरा उनका संचयी प्रभाव है। एक 100 KB का लीक ध्यान देने योग्य नहीं है, लेकिन ऐसे 100 लीक 10 MB घेर लेते हैं, और एप्लिकेशन बार-बार GC के कारण सुस्त होने लगता है। लीक का महत्वपूर्ण द्रव्यमान OutOfMemoryError और एप्लिकेशन क्रैश की ओर ले जाता है। लीक के लक्षण: Profiler ग्राफ पर मेमोरी खपत में लगातार वृद्धि, STW (Stop The World) के साथ बार-बार GC ठहराव और UI प्रदर्शन में गिरावट।
पांच प्रकार के लीक मोबाइल डेवलपमेंट में 95% मामलों को कवर करते हैं। प्रत्येक का अपना कारण और कोड में विशिष्ट पैटर्न होता है।
सबसे प्रसिद्ध लीक Android में — Activity या Context का स्थिर संदर्भ संग्रहीत करना। विशिष्ट कोड: एक स्थिर Activity फ़ील्ड जो onDestroy() पर शून्य नहीं की जाती। जब तक स्थिर फ़ील्ड जीवित है, पूरी Activity अपने View ट्री के साथ जीवित रहती है, जो 1–10 MB घेर सकती है। यह क्लासिक लीक है जिसे LeakCanary सबसे पहले ढूंढता है।
समाधान: Activity या Context को कभी भी स्थिर फ़ील्ड में संग्रहीत न करें। उन सिंगलटन के लिए Application Context का उपयोग करें जो Activity से अधिक जीवित रहते हैं। यदि Activity का संदर्भ चाहिए, तो WeakReference<Activity> का उपयोग करें।
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
अनाम कक्षाएं और गैर-स्थिर आंतरिक कक्षाएं अंतर्निहित रूप से समाहित करने वाली कक्षा का संदर्भ रखती हैं। Handler को दिया गया Runnable जो onDestroy() के बाद निष्पादित होता है, पूरी Activity को रोके रखता है। Activity को कैप्चर करने वाला Retrofit कॉलबैक भी ऐसा ही करता है। यह लीक का सबसे कपटी प्रकार है — अंतर्निहित संदर्भ कोड में दिखाई नहीं देता।
Kotlin object एक्सप्रेशन और लैम्ब्डा भी बाहरी कक्षा के संदर्भ कैप्चर करते हैं। आंतरिक कक्षाओं को स्थिर (या Kotlin में टॉप-लेवल) बनाएं और बाहरी संदर्भ WeakReference के माध्यम से पास करें। लैम्ब्डा के लिए viewLifecycleOwner के साथ Lifecycle-aware दृष्टिकोण का उपयोग करें।
सिस्टम सेवाओं की सब्सक्राइब करना बिना अनसब्सक्राइब किए — यह सीधा लीक है। SensorManager, LocationManager, NotificationListener जो onResume() में पंजीकृत हैं और onPause() में unregister कॉल नहीं करते, Activity को रोके रखते हैं। इसी तरह: RxJava Disposable जो CompositeDisposable में नहीं जोड़ा गया, और GlobalScope के माध्यम से लॉन्च किया गया कोरूटीन।
Lifecycle-aware घटकों का उपयोग करें: LifecycleOwner के साथ observe() onDestroy() पर स्वचालित रूप से अनसब्सक्राइब हो जाता है। RxJava के लिए — DisposableObserver के साथ viewLifecycleOwner.lifecycle.addObserver। कोरूटीन के लिए — lifecycleScope.launch() जीवनचक्र से बंधा होता है।
// Lifecycle के माध्यम से स्वचालित अनसब्सक्राइब
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// lifecycleScope के साथ कोरूटीन
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Bitmap हीप मेमोरी की एक महत्वपूर्ण मात्रा घेरता है: एक FullHD बिटमैप 1920 × 1080 × 4 बाइट = 8.3 MB है। यदि सूची के प्रत्येक आइटम के लिए Bitmap बनाया जाता है और छिपाने पर recycle() नहीं कहा जाता, तो मेमोरी जल्दी समाप्त हो जाती है। Android के पुराने संस्करणों (3.0 से पहले) पर Bitmap नेटिव मेमोरी में संग्रहीत होता था, लेकिन आधुनिक संस्करणों पर यह Dalvik/ART हीप में है, और GC इसे केवल तभी मुक्त कर सकता है जब कोई स्ट्रांग रेफरेंस न हो।
छवियों को लोड करने के लिए Glide या Coil का उपयोग करें — ये लाइब्रेरीज़ कैशिंग और रीसाइक्लिंग को स्वचालित रूप से प्रबंधित करती हैं। यदि सीधे Bitmap के साथ काम कर रहे हैं, तो बड़ी छवियों के लिए bitmap.recycle() कॉल करें जो अब प्रदर्शित नहीं हो रही हैं, और छोटी प्रतियां लोड करने के लिए inSampleSize का उपयोग करें।
Fragment के दो जीवनचक्र हैं: Fragment का अपना और उसके View का। onDestroyView() के बाद View ट्री नष्ट हो जाता है, लेकिन Fragment स्वयं मेमोरी में रह सकता है यदि कोई बाहरी संदर्भ हो। एक विशिष्ट गलती ViewPager एडेप्टर या नेविगेशन ग्राफ में Fragment का संदर्भ संग्रहीत करना है जो नष्ट होने पर साफ़ नहीं होता।
लंबे समय तक जीवित रहने वाले ऑब्जेक्ट के फ़ील्ड में Fragment का संदर्भ कभी संग्रहीत न करें। नेस्टेड फ़्रैगमेंट के लिए childFragmentManager का उपयोग करें और उनके बीच डेटा स्थानांतरण के लिए LifecycleOwner के साथ observe() का उपयोग करें। ViewPager2 ने इस समस्या को API स्तर पर हल किया: FragmentTransactionAdapter जीवनचक्र को सही ढंग से प्रबंधित करता है।
लीक का पता लगाने के लिए दो तथ्यों की पुष्टि आवश्यक है: मेमोरी अपेक्षित जीवनकाल के बाद वापस नहीं आती है और एक निश्चित प्रकार की ऑब्जेक्ट की संख्या बिना घटे बढ़ती है। निदान प्रक्रिया में तीन चरण शामिल हैं।
पहला चरण — Android Studio में Memory Profiler के माध्यम से दृश्य जांच। Memory टैब खोलें, लक्ष्य कार्रवाई करें (स्क्रीन खोलें और बंद करें), GC (Garbage Collection) दबाएं और देखें कि क्या मेमोरी प्रारंभिक स्तर पर वापस आती है। यदि 3–4 खोलने-बंद करने के चक्रों के बाद मेमोरी लगातार बढ़ती है — तो लीक है।
दूसरा चरण — Heap Dump लेना। Memory Profiler में Dump Java Heap दबाएं। परिणामी .hprof फ़ाइल को Android Studio में खोलें: आप हीप में सभी ऑब्जेक्ट को आकार और संदर्भों के साथ देखेंगे। उन कक्षाओं को खोजें जिनकी संख्या स्क्रीन बंद करने के बाद शून्य होनी चाहिए। उदाहरण के लिए, बंद करने के बाद संख्या 2 वाली MainActivity — स्पष्ट लीक है।
तीसरा चरण — Retained Size और GC Root का विश्लेषण। Android Studio में Retained Size का विश्लेषण करें: यदि आप इस ऑब्जेक्ट को हटाते हैं तो कितनी मेमोरी मुक्त होगी। GC Root से ऑब्जेक्ट तक का पथ दिखाता है कि इसे क्या रोके हुए है: Static field → HashMap → Activity — और आप लीक बिंदु देखते हैं। Reference विजेट पैनल ऑब्जेक्ट के सभी धारकों को दिखाता है।
चार उपकरण स्वचालित पहचान से लेकर गहन Heap Dump विश्लेषण तक लीक खोज को कवर करते हैं।
| उपकरण | विधि | परिणाम प्रारूप |
|---|---|---|
| LeakCanary | स्वचालित निगरानी | Heap Dump + लीक stack trace |
| Android Memory Profiler | मैन्युअल निगरानी | मेमोरी ग्राफ + Heap Dump |
| MAT (Eclipse) | गहन विश्लेषण | Dominator Tree रिपोर्ट + GC Root पथ |
| Perfetto | सिस्टम-व्यापी ट्रेसिंग | समयरेखा + नेटिव मेमोरी |
LeakCanary किसी भी Android प्रोजेक्ट के लिए अनिवार्य है। यह Activity/Fragment जीवनचक्र के अंत में स्वचालित रूप से लीक का पता लगाता है और stack trace के साथ सटीक लीक स्थान दिखाता है। एकीकरण: build.gradle में एक पंक्ति। LeakCanary 2.x को मैन्युअल आरंभीकरण की आवश्यकता नहीं है — यह स्वचालित रूप से Application Watcher पंजीकृत करता है।
लीक की रोकथाम नियमों और उपकरणों के एक सेट के माध्यम से विकास प्रक्रिया में बनाई जाती है जो हर चरण में कोड की जांच करते हैं।
कभी भी Activity, Fragment या View का संदर्भ स्थिर फ़ील्ड, सिंगलटन या लंबे समय तक जीवित रहने वाले ऑब्जेक्ट में संग्रहीत न करें। यदि संदर्भ अपरिहार्य है, तो WeakReference का उपयोग करें या ViewModel के माध्यम से डेटा संग्रहीत करें, जो ठीक उतने समय तक जीवित रहता है जितना आवश्यक है और सीधे View को नहीं रखता।
Android Architecture Components से ViewModel और LiveData आर्किटेक्चर स्तर पर जीवनचक्र समस्या का समाधान करते हैं। ViewModel स्क्रीन रोटेशन से बच जाता है और इसमें View संदर्भ नहीं होते। LiveData onDestroy() पर स्वचालित रूप से पर्यवेक्षक को अनसब्सक्राइब करता है। सिस्टम सेवाओं की मैन्युअल सब्सक्रिप्शन के बजाय इनका उपयोग करें।
कोड समीक्षा में ध्यान दें: Context/View प्रकार वाले स्थिर फ़ील्ड, अनाम कक्षाएं, Activity को कैप्चर करने वाले लैम्ब्डा, मैन्युअल सब्सक्रिप्शन, बिना composite के RxJava disposable, Bundle के माध्यम से Fragment संग्रहीत करना। Kotlin में, जीवनचक्र बंधन के बिना launch पर कोरूटीन की अतिरिक्त जांच करें।
LeakCanary परीक्षण पाइपलाइन के भाग के रूप में काम कर सकता है: LeakCanary के साथ स्वीकृति परीक्षण चलाएं और यदि लीक मिलता है तो बिल्ड विफल करें। यह लीक को प्रोडक्शन तक पहुंचने से रोकता है। Android Lint के StaticFieldLeak नियम के साथ जांच को पूरक करें — यह स्थिर विश्लेषण स्तर पर संभावित लीक ढूंढता है।
// परीक्षणों में LeakCanary
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // यदि लीक है तो विफल करें
}
}
अक्सर पूछे जाने वाले प्रश्न
लीक कारण है, और OutOfMemoryError परिणाम है। एक लीक OOM की ओर नहीं ले जाता, लेकिन दर्जनों लीक का संचय Heap को समाप्त कर देता है। OOM एक घातक अपवाद है, जबकि लीक एक पैटर्न है जो समय के साथ उसकी ओर ले जाता है।
Android Memory Profiler के माध्यम से: स्क्रीन को 5 बार खोलें और बंद करें, प्रत्येक बंद करने के बाद GC को कॉल करें। यदि मेमोरी आधार स्तर पर वापस नहीं आती — लीक है। Heap Dump लें और सूची में वह Activity कक्षा खोजें जिसकी संख्या बंद करने के बाद 0 से अधिक है।
आंशिक रूप से। Kotlin null-safety समस्या को हल करता है लेकिन स्ट्रांग रेफरेंस का प्रबंधन नहीं करता। lifecycleScope और viewModelScope वाले कोरूटीन पृष्ठभूमि कार्यों से लीक को रोकते हैं, जबकि sealed class और data class लीक की ओर ले जाने वाली स्थितियों की संख्या कम करते हैं। मुख्य सुरक्षा भाषा की विशेषताएं नहीं, बल्कि आर्किटेक्चरल पैटर्न हैं।
LeakCanary कभी-कभी गलत सकारात्मक परिणाम देता है: कोई ऑब्जेक्ट अस्थायी रूप से सिस्टम द्वारा रोका जा सकता है (उदाहरण के लिए, InputMethodManager अंतिम View को रोकता है)। मैन्युअल रूप से जांचें: यदि Retained Size < 1 KB है और GC Root एक सिस्टम सेवा है, तो यह संभवतः गलत सकारात्मक है।
नहीं। लीक GC वाले किसी भी प्लेटफॉर्म पर संभव है: iOS (Swift/Objective-C), Flutter (Dart), वेब ब्राउज़र (JavaScript)। तंत्र समान हैं — GC Root से स्ट्रांग रेफरेंस। iOS पर, ARC स्वचालित रूप से मेमोरी का प्रबंधन करता है, लेकिन ऑब्जेक्ट के बीच retain cycle वही लीक पैदा करता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें