मोबाइल एप्लिकेशन में मेमोरी लीक — यह क्या है, कारण और पता लगाने के तरीके

लेखक: IT Sectr प्रकाशित: 2026-03-29 पढ़ने का समय: 9 मिनट

मेमोरी लीक (Memory Leak) वह स्थिति है जब एप्लिकेशन उन ऑब्जेक्ट्स के संदर्भ (references) को रोके रखता है जिनकी अब आवश्यकता नहीं है, जिससे गार्बेज कलेक्टर (GC) व्याप्त मेमोरी को मुक्त नहीं कर पाता। LeakCanary के अनुसार, अच्छी तरह से लिखे गए एप्लिकेशन में भी प्रति 10,000 लाइन कोड में 3–5 लीक पाए जाते हैं। प्रत्येक लीक धीरे-धीरे उपलब्ध मेमोरी को कम करता है, जिससे सुस्ती और OutOfMemoryError होता है।

मुख्य बिंदु

  • Memory Leak — एक ऑब्जेक्ट मेमोरी में रहता है, भले ही एप्लिकेशन लॉजिक से उसका कोई सक्रिय संदर्भ न हो
  • स्थिर संदर्भ Activity या Context के — Android में लीक का सबसे आम कारण
  • LeakCanary — Android में स्वचालित लीक पहचान के लिए मानक उपकरण
  • WeakReference और Application Context — लीक रोकने की बुनियादी तकनीकें
  • Lifecycle-aware घटक सब्सक्रिप्शन से संबंधित लीक की पूरी श्रेणी को खत्म करते हैं

मेमोरी लीक क्या है

मेमोरी लीक (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% मामलों को कवर करते हैं। प्रत्येक का अपना कारण और कोड में विशिष्ट पैटर्न होता है।

Activity या Context का स्थिर संदर्भ

सबसे प्रसिद्ध लीक Android में — Activity या Context का स्थिर संदर्भ संग्रहीत करना। विशिष्ट कोड: एक स्थिर Activity फ़ील्ड जो onDestroy() पर शून्य नहीं की जाती। जब तक स्थिर फ़ील्ड जीवित है, पूरी Activity अपने View ट्री के साथ जीवित रहती है, जो 1–10 MB घेर सकती है। यह क्लासिक लीक है जिसे LeakCanary सबसे पहले ढूंढता है।

समाधान: Activity या Context को कभी भी स्थिर फ़ील्ड में संग्रहीत न करें। उन सिंगलटन के लिए Application Context का उपयोग करें जो Activity से अधिक जीवित रहते हैं। यदि Activity का संदर्भ चाहिए, तो WeakReference<Activity> का उपयोग करें।

kotlin
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() जीवनचक्र से बंधा होता है।

kotlin
// Lifecycle के माध्यम से स्वचालित अनसब्सक्राइब
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// lifecycleScope के साथ कोरूटीन
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

बिना recycle के Bitmap

Bitmap हीप मेमोरी की एक महत्वपूर्ण मात्रा घेरता है: एक FullHD बिटमैप 1920 × 1080 × 4 बाइट = 8.3 MB है। यदि सूची के प्रत्येक आइटम के लिए Bitmap बनाया जाता है और छिपाने पर recycle() नहीं कहा जाता, तो मेमोरी जल्दी समाप्त हो जाती है। Android के पुराने संस्करणों (3.0 से पहले) पर Bitmap नेटिव मेमोरी में संग्रहीत होता था, लेकिन आधुनिक संस्करणों पर यह Dalvik/ART हीप में है, और GC इसे केवल तभी मुक्त कर सकता है जब कोई स्ट्रांग रेफरेंस न हो।

छवियों को लोड करने के लिए Glide या Coil का उपयोग करें — ये लाइब्रेरीज़ कैशिंग और रीसाइक्लिंग को स्वचालित रूप से प्रबंधित करती हैं। यदि सीधे Bitmap के साथ काम कर रहे हैं, तो बड़ी छवियों के लिए bitmap.recycle() कॉल करें जो अब प्रदर्शित नहीं हो रही हैं, और छोटी प्रतियां लोड करने के लिए inSampleSize का उपयोग करें।

onDestroyView के बाद Fragment संदर्भ

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 को नहीं रखता।

Lifecycle-aware आर्किटेक्चर

Android Architecture Components से ViewModel और LiveData आर्किटेक्चर स्तर पर जीवनचक्र समस्या का समाधान करते हैं। ViewModel स्क्रीन रोटेशन से बच जाता है और इसमें View संदर्भ नहीं होते। LiveData onDestroy() पर स्वचालित रूप से पर्यवेक्षक को अनसब्सक्राइब करता है। सिस्टम सेवाओं की मैन्युअल सब्सक्रिप्शन के बजाय इनका उपयोग करें।

GC Root पर ध्यान केंद्रित कोड समीक्षा

कोड समीक्षा में ध्यान दें: Context/View प्रकार वाले स्थिर फ़ील्ड, अनाम कक्षाएं, Activity को कैप्चर करने वाले लैम्ब्डा, मैन्युअल सब्सक्रिप्शन, बिना composite के RxJava disposable, Bundle के माध्यम से Fragment संग्रहीत करना। Kotlin में, जीवनचक्र बंधन के बिना launch पर कोरूटीन की अतिरिक्त जांच करें।

CI में स्वचालित जांच

LeakCanary परीक्षण पाइपलाइन के भाग के रूप में काम कर सकता है: LeakCanary के साथ स्वीकृति परीक्षण चलाएं और यदि लीक मिलता है तो बिल्ड विफल करें। यह लीक को प्रोडक्शन तक पहुंचने से रोकता है। Android Lint के StaticFieldLeak नियम के साथ जांच को पूरक करें — यह स्थिर विश्लेषण स्तर पर संभावित लीक ढूंढता है।

kotlin
// परीक्षणों में LeakCanary
class LeakTest {
    @Test
    fun activityShouldNotLeak() {
        ActivityScenario.launch(MainActivity::class.java)
            .close()
        LeakAssertions.assertNoLeak() // यदि लीक है तो विफल करें
    }
}

अक्सर पूछे जाने वाले प्रश्न

मेमोरी लीक OutOfMemoryError से कैसे अलग है?

लीक कारण है, और OutOfMemoryError परिणाम है। एक लीक OOM की ओर नहीं ले जाता, लेकिन दर्जनों लीक का संचय Heap को समाप्त कर देता है। OOM एक घातक अपवाद है, जबकि लीक एक पैटर्न है जो समय के साथ उसकी ओर ले जाता है।

LeakCanary के बिना लीक कैसे खोजें?

Android Memory Profiler के माध्यम से: स्क्रीन को 5 बार खोलें और बंद करें, प्रत्येक बंद करने के बाद GC को कॉल करें। यदि मेमोरी आधार स्तर पर वापस नहीं आती — लीक है। Heap Dump लें और सूची में वह Activity कक्षा खोजें जिसकी संख्या बंद करने के बाद 0 से अधिक है।

क्या Kotlin भाषा स्तर पर लीक को रोक सकता है?

आंशिक रूप से। Kotlin null-safety समस्या को हल करता है लेकिन स्ट्रांग रेफरेंस का प्रबंधन नहीं करता। lifecycleScope और viewModelScope वाले कोरूटीन पृष्ठभूमि कार्यों से लीक को रोकते हैं, जबकि sealed class और data class लीक की ओर ले जाने वाली स्थितियों की संख्या कम करते हैं। मुख्य सुरक्षा भाषा की विशेषताएं नहीं, बल्कि आर्किटेक्चरल पैटर्न हैं।

LeakCanary ऐसा लीक क्यों ढूंढता है जो मौजूद नहीं है?

LeakCanary कभी-कभी गलत सकारात्मक परिणाम देता है: कोई ऑब्जेक्ट अस्थायी रूप से सिस्टम द्वारा रोका जा सकता है (उदाहरण के लिए, InputMethodManager अंतिम View को रोकता है)। मैन्युअल रूप से जांचें: यदि Retained Size < 1 KB है और GC Root एक सिस्टम सेवा है, तो यह संभवतः गलत सकारात्मक है।

क्या मेमोरी लीक केवल Android पर होती है?

नहीं। लीक GC वाले किसी भी प्लेटफॉर्म पर संभव है: iOS (Swift/Objective-C), Flutter (Dart), वेब ब्राउज़र (JavaScript)। तंत्र समान हैं — GC Root से स्ट्रांग रेफरेंस। iOS पर, ARC स्वचालित रूप से मेमोरी का प्रबंधन करता है, लेकिन ऑब्जेक्ट के बीच retain cycle वही लीक पैदा करता है।

सारांश

  • Memory Leak — एक ऑब्जेक्ट जिसे GC भूली हुई स्ट्रांग रेफरेंस के कारण मुक्त नहीं कर सकता
  • स्थिर संदर्भ Activity और Context के — लीक का सबसे आम कारण
  • अंतर्निहित संदर्भ अनाम कक्षाओं, लैम्ब्डा और RxJava सब्सक्रिप्शन के माध्यम से — स्पष्ट संदर्भों से अधिक कपटी
  • LeakCanary स्वचालित रूप से लीक ढूंढता है और सटीक stack trace दिखाता है
  • Lifecycle-aware घटक (ViewModel, LiveData, lifecycleScope) लीक की एक श्रेणी को समाप्त करते हैं
  • Heap Dump और Retained Size विश्लेषण — मैन्युअल निदान की मुख्य विधि
  • रोकथाम में स्ट्रांग रेफरेंस पर केंद्रित कोड समीक्षा और LeakCanary के साथ CI जांच शामिल है

हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे

IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।

परियोजना पर चर्चा करें

यह भी पढ़ें