মেমরি লিক: এটি কী, সাধারণ পরিস্থিতি এবং রোগনির্ণয়

লেখক: IT Sectr প্রকাশিত: 2026-07-29 পড়ার সময়: 10 মিনিট

মেমরি লিক (memory leak) — এমন একটি পরিস্থিতি যেখানে অ্যাপ্লিকেশন এমন অবজেক্টগুলির দ্বারা দখলকৃত মেমরি মুক্ত করে না যেগুলির আর প্রয়োজন নেই। মোবাইল ডেভেলপমেন্টে এটি বিশেষভাবে গুরুত্বপূর্ণ: সীমিত heap এবং swap-এর অনুপস্থিতি OutOfMemoryError এবং অ্যাপ্লিকেশন ক্র্যাশের দিকে নিয়ে যায়। Purdue University (2022) অনুসারে, Google Play-তে 35% Android অ্যাপ্লিকেশনে কমপক্ষে একটি মেমরি লিক রয়েছে। আসুন সাধারণ পরিস্থিতি, রোগনির্ণয়ের সরঞ্জাম এবং সমাধানের পদ্ধতি নিয়ে আলোচনা করি।

মুখ্য বিষয়

  • GC Root — প্রবেশ বিন্দু যার মাধ্যমে আবর্জনা সংগ্রহকারী জীবিত অবজেক্ট নির্ধারণ করে
  • Context লিক — সিঙ্গলটনে Activity Context পাস করলে পুরো View hierarchy আটকে যায়
  • Handler postDelayed সহ — Activity ধ্বংস হলে, Handler তাকে GC-তে যেতে বাধা দেয়
  • Heap dump — MAT বা Android Profiler-এর মাধ্যমে লিক বিশ্লেষণের প্রধান পদ্ধতি
  • SoftReference — মেমরির অভাব হলে স্বয়ংক্রিয় পরিষ্কার হওয়া ক্যাশের জন্য WeakReference-এর বিকল্প

মোবাইল অ্যাপ্লিকেশনে মেমরি লিক কী?

মেমরি লিক — এমন একটি পরিস্থিতি যেখানে বরাদ্দকৃত মেমরি প্রোগ্রামের অবজেক্টটির আর প্রয়োজন না হওয়ার পরেও সিস্টেমে ফেরত দেওয়া হয় না। আবর্জনা সংগ্রহকারী এই ধরনের অবজেক্টকে জীবিত মনে করে কারণ GC Root থেকে একটি সক্রিয় রেফারেন্স চেইন তার দিকে নির্দেশ করছে।

Java/Kotlin-এ, আবর্জনা সংগ্রহকারী স্বয়ংক্রিয়ভাবে কাজ করে, কিন্তু এটি নির্ধারণ করতে পারে না যে একটি অবজেক্ট যৌক্তিকভাবে অপ্রয়োজনীয় যদি তার একটি প্রযুক্তিগত রেফারেন্স বিদ্যমান থাকে। ডেভেলপারকে অপ্রয়োজনীয় সংযোগগুলি স্পষ্টভাবে ভেঙে দিতে হবে। Swift/Objective-C-এ, ARC স্বয়ংক্রিয়ভাবে রেফারেন্স গণনা করে, কিন্তু retain cycles কাউন্টারকে শূন্যে পৌঁছাতে বাধা দেয়।

লিকের প্রধান বিপদ হল সঞ্চয়ী প্রভাব। প্রতিটি লিক অল্প পরিমাণ মেমরি খরচ করে, কিন্তু বারবার স্ক্রিন পরিবর্তনের (স্ক্রিন রোটেশন, Activity খোলা/বন্ধ করা) সাথে লিক জমতে থাকে যতক্ষণ না heap সীমা শেষ হয়ে যায়।

লিক ব্লোট থেকে কীভাবে আলাদা?

লিক — অবজেক্ট কোডের কাছে অপ্রাপ্য কিন্তু GC দ্বারা অপসারিত হয়নি। ব্লোট — অবজেক্ট যৌক্তিকভাবে প্রয়োজনীয় কিন্তু অতিরিক্ত পরিমাণে সংরক্ষিত। ব্লোটের উদাহরণ: 30 MB ওয়ার্কিং সেট সহ 100 MB-র ইমেজ ক্যাশ। উভয় সমস্যাই OOM-এর দিকে নিয়ে যায়, কিন্তু কারণ এবং চিকিৎসার পদ্ধতি ভিন্ন।

আবর্জনা সংগ্রহকারী কীভাবে কাজ করে এবং লিক কেন হয়?

ART (Android Runtime) সমবর্তী কমপ্যাকশন সহ প্রজন্মগত আবর্জনা সংগ্রহ ব্যবহার করে। মেমরি তরুণ প্রজন্ম (Young), পুরাতন প্রজন্ম (Old) এবং বড় অবজেক্ট (Large)-এ বিভক্ত। বেশ কয়েকটি GC চক্র থেকে বেঁচে যাওয়া অবজেক্টগুলি Old generation-এ চলে যায়, যেখানে সংগ্রহ কম ঘন ঘন হয় — এটি সাধারণ চক্রগুলিকে দ্রুততর করে।

GC শুরু হয় যখন heap একটি নির্দিষ্ট অধিকার সীমায় (সাধারণত 75-85%) পৌঁছায়। GC-র সময়, অ্যাপ্লিকেশনের সমস্ত থ্রেড থামিয়ে দেওয়া হয় (STW — Stop The World)। যত বেশি জীবিত অবজেক্ট, তত দীর্ঘ বিরতি। লিক জীবিত অবজেক্টের সংখ্যা বাড়ায়, GC বিরতি দীর্ঘায়িত করে।

সংগ্রাহক GC Roots থেকে গ্রাফ ট্রাভার্স করে জীবিত অবজেক্ট নির্ধারণ করে: স্ট্যাটিক ফিল্ড, সক্রিয় থ্রেডের স্ট্যাক ভেরিয়েবল, JNI রেফারেন্স। এই শিকড়গুলি থেকে রেফারেন্সের মাধ্যমে পৌঁছানো যায় এমন যেকোনো অবজেক্ট জীবিত বলে বিবেচিত হয় — এমনকি যদি ডেভেলপার জানে যে এটি আর প্রয়োজন নেই।

kotlin
// উদাহরণ: GC Root হিসাবে স্ট্যাটিক সংগ্রহ — স্থায়ী লিক
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference GC বাধা দেয় না — সঠিক আচরণ
    }
}

WeakReference সমস্যার সমাধান করে: GC জীবিত অবজেক্ট নির্ধারণ করার সময় দুর্বল রেফারেন্স উপেক্ষা করে। যদি একটি অবজেক্টে কেবল দুর্বল রেফারেন্স থাকে, তবে এটি নিকটতম GC চক্রে সংগ্রহ করা হবে।

Android এবং iOS-এ সাধারণ লিক পরিস্থিতি

Activity Context — Android-এ সবচেয়ে বিস্তৃত লিক পরিস্থিতি। যদি একটি সিঙ্গলটন, স্ট্যাটিক ফিল্ড বা দীর্ঘস্থায়ী পরিষেবা Activity Context-এর রেফারেন্স রাখে, তাহলে সমস্ত Views সহ পুরো Activity GC দ্বারা সংগ্রহ করা যায় না। সমাধান: দীর্ঘস্থায়ী অবজেক্টের জন্য Application Context ব্যবহার করুন।

Handler এবং প্রেরিত বার্তা — Handler.postDelayed(runnable, delay) Main Looper সারিতে একটি বার্তা রাখে। যদি বিলম্ব শেষ হওয়ার আগে Activity ধ্বংস হয়ে যায়, বার্তাটি এখনও সারিতে থাকে এবং Runnable → anonymous class → outer class (Activity)-এর মাধ্যমে রেফারেন্স রাখে।

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // বাধ্যতামূলক: সারি পরিষ্কার করুন
        super.onPause()
    }
}

অভ্যন্তরীণ ক্লাস — একটি নন-স্ট্যাটিক অভ্যন্তরীণ ক্লাসের বাইরের ক্লাসের ইনস্ট্যান্সের একটি অন্তর্নিহিত রেফারেন্স থাকে। যদি বাইরের ক্লাসটি Activity হয় এবং অভ্যন্তরীণ ক্লাসটি বাইরে কোথাও পাস করা হয় (উদাহরণস্বরূপ, RecyclerView.Adapter-এ), তাহলে Activity সংগ্রহ করা যায় না।

  • TimerTask এবং ScheduledExecutorService — Activity ধ্বংসের আগে নির্ধারিত কাজ
  • BroadcastReceiver — onPause/onDestroy-তে নিবন্ধনমুক্ত না হলে Context ধরে রাখে
  • View রেফারেন্স সহ ViewModel — ViewModel Activity-র চেয়ে বেশি বাঁচে, View-এর রেফারেন্স লিকের দিকে নিয়ে যায়
  • Retrofit Call — Call বাতিল না হলে, প্রতিক্রিয়া ধ্বংস হওয়া Fragment-এ আসে

মেমরি লিক রোগনির্ণয়ের সরঞ্জাম

Android Studio Memory Profiler — রিয়েল-টাইম heap পর্যবেক্ষণের জন্য অন্তর্নির্মিত সরঞ্জাম। ব্যবহৃত মেমরির গ্রাফ, বরাদ্দের সংখ্যা এবং প্রকার অনুসারে অবজেক্ট দেখায়। MAT-তে বিশ্লেষণের জন্য heap dump রেকর্ড এবং HPROF ফরম্যাটে রপ্তানি করার অনুমতি দেয়।

Eclipse MAT (Memory Analyzer Tool) — ডেস্কটপ heap dump বিশ্লেষক। স্বয়ংক্রিয়ভাবে Leak Suspects রিপোর্ট তৈরি করে, যা সবচেয়ে বড় retained size সহ অবজেক্টগুলিকে হাইলাইট করে এবং প্রতিটি সন্দেহজনক অবজেক্টের জন্য সম্ভাব্য GC root chain সুপারিশ করে।

Xcode Memory Graph Debugger — iOS-এর জন্য। অ্যাপ্লিকেশন থামায় এবং অবজেক্ট গ্রাফ可视化 করে। Retain cycles লাল রঙে হাইলাইট করা হয়; যেকোনো অবজেক্টে ক্লিক করে তার retain count এবং রেফারেন্স দেখা যায়।

সরঞ্জামক্ষমতাজটিলতা
Memory Profilerরিয়েল-টাইম গ্রাফ, heap dump, Object Allocation Trackingনিম্ন
Eclipse MATDominator tree, Leak Suspects, OQL কোয়েরিমধ্যম
LeakCanaryস্বয়ংক্রিয় সনাক্তকরণ, বিজ্ঞপ্তিতে লিক ট্রেসসর্বনিম্ন
Xcode Memory Graphretain cycles-এর ভিজুয়াল গ্রাফ, জীবিত অবজেক্ট তালিকানিম্ন

Uber Engineering Blog অনুসারে, CI/CD পাইপলাইনে স্বয়ংক্রিয় মেমরি প্রোফাইলিং (LeakCanary + heap dump বিশ্লেষণ) একীভূত করলে 3 মাসের মধ্যে উৎপাদনে মেমরি-সম্পর্কিত ঘটনা 60% কমে যায়।

লিক দূর করার পদ্ধতি

Context পরিবর্তন করুন — যদি কোনো অবজেক্ট Activity-র চেয়ে বেশি বাঁচে, তাহলে applicationContext ব্যবহার করুন। সমস্ত দীর্ঘস্থায়ী অবজেক্ট (সিঙ্গলটন, রিপোজিটরি, ডাটাবেস হেল্পার) Application Context পাওয়া উচিত, Activity Context নয়। ব্যতিক্রম: UI কম্পোনেন্ট যাদের Activity-নির্দিষ্ট থিম বা রিসোর্সে অ্যাক্সেস প্রয়োজন।

Lifecycle-aware কম্পোনেন্ট — LifecycleObserver, DefaultLifecycleObserver বা রিঅ্যাকটিভ এক্সটেনশন ব্যবহার onDestroy-তে স্বয়ংক্রিয়ভাবে সাবস্ক্রিপশন বাতিল করে। Android Jetpack lifecycleScope এবং viewModelScope প্রদান করে, যা সংশ্লিষ্ট লাইফসাইকেল ইভেন্ট দ্বারা পরিষ্কার করা হয়।

স্ট্যাটিক অভ্যন্তরীণ ক্লাস — যদি অভ্যন্তরীণ ক্লাসের বাইরের ক্লাসের ফিল্ডে অ্যাক্সেসের প্রয়োজন না হয়, তাহলে এটিকে static করুন। স্ট্যাটিক অভ্যন্তরীণ ক্লাসের বাইরের ক্লাসে কোনো অন্তর্নিহিত রেফারেন্স থাকে না। যদি অ্যাক্সেস প্রয়োজন হয়, তবে স্পষ্ট রেফারেন্সের জন্য WeakReference ব্যবহার করুন।

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ নন-স্ট্যাটিক অভ্যন্তরীণ ক্লাস — MyActivity-র অন্তর্নিহিত রেফারেন্স
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ স্ট্যাটিক অভ্যন্তরীণ ক্লাস — কোনো অন্তর্নিহিত রেফারেন্স নাই
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

iOS-এ, ক্যাপচার তালিকা ব্যবহার করুন: ক্লোজারে [weak self] যা তাদের নির্মাতার চেয়ে বেশি বাঁচতে পারে। ডেলিগেটের জন্য, দুর্বল রেফারেন্স ব্যবহার করুন (weak var delegate)। সেই ক্লোজারগুলির জন্য যেগুলি শুধুমাত্র self-এর জীবনকালের মধ্যে কল হওয়ার গ্যারান্টিযুক্ত, [unowned self] ব্যবহার করা যেতে পারে, কিন্তু সতর্কতার সাথে — ডিলোকেটেড অবজেক্ট অ্যাক্সেস করলে ক্র্যাশ হবে।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

বিশেষ সরঞ্জাম ছাড়া লিক কীভাবে খুঁজবেন?

Android-এ, বেশ কয়েকটি স্ক্রিন পরিবর্তন (Activity A → B → A → B) করুন এবং adb shell dumpsys meminfo package_name পরীক্ষা করুন। যদি Total PSS স্থিরভাবে বাড়তে থাকে এবং মূল মানে ফিরে না আসে — তাহলে লিক আছে। iOS-এ, একইভাবে: ভিজুয়াল পরিদর্শনের জন্য Xcode-এ Debug Memory Graph ব্যবহার করুন।

Kotlin করুটিন কি লিকের কারণ হতে পারে?

হ্যাঁ, যদি কম্পোনেন্ট ধ্বংস হওয়ার সময় CoroutineScope বাতিল না করা হয়। GlobalScope-এ লঞ্চ করা করুটিন Activity-র finish()-এর পরেও চলতে থাকে। সমাধান: viewModelScope (onCleared-এ বাতিল) বা lifecycleScope (onDestroy-এ বাতিল) ব্যবহার করুন। কাস্টম স্কোপের জন্য, LifecycleOwner-এর মাধ্যমে lifecycle-aware স্কোপ তৈরি করুন।

Bitmap কীভাবে লিককে প্রভাবিত করে?

Bitmap পিক্সেল ডেটা Java heap-এর পরিবর্তে native heap-এ সংরক্ষণ করে। এর মানে হল Java GC Bitmap-এর প্রকৃত আকার দেখে না। যদি Bitmap-এ recycle() কল না করা হয় বা রেফারেন্স শূন্য না করা হয়, তাহলে native মেমরি মুক্ত হয় না। ছোট কপি লোড করার জন্য BitmapFactory-কে inSampleSize সহ এবং স্বয়ংক্রিয় ক্যাশ ব্যবস্থাপনার জন্য Glide/Coil ব্যবহার করুন।

স্ট্যাটিক ফিল্ডের মাধ্যমে লিক কী?

স্ট্যাটিক ফিল্ড — একটি GC Root। এটি ততক্ষণ বেঁচে থাকে যতক্ষণ ক্লাস লোড করা আছে (Android-এ — যতক্ষণ Process বেঁচে আছে)। যদি একটি স্ট্যাটিক ফিল্ড Activity, Bitmap, View বা অন্য কোনো ভারী অবজেক্টকে রেফারেন্স করে, তবে সেই অবজেক্টটি কখনো GC দ্বারা সংগ্রহ করা হবে না। স্ট্যাটিক ফিল্ড একটি চিরন্তন রেফারেন্স। সমাধান: শুধুমাত্র WeakReference সংরক্ষণ করুন বা onDestroy-এ স্ট্যাটিক ফিল্ড শূন্য করুন।

ARC সহ iOS-এ লিক এড়াবেন কীভাবে?

ARC স্বয়ংক্রিয়ভাবে অবজেক্ট মুক্ত করে যখন শক্তিশালী রেফারেন্স গণনা শূন্যে নেমে আসে। ARC-এর অধীনে লিকের একমাত্র উপায় হল retain cycle। সর্বদা parent→child রেফারেন্সের জন্য weak ব্যবহার করুন যেখানে child parent-এর চেয়ে বেশি বাঁচতে পারে (ডেলিগেট, data source)। ক্লোজারের জন্য, ক্যাপচার তালিকা [weak self] ব্যবহার করুন এবং ক্লোজারের ভিতরে self nil কিনা পরীক্ষা করুন।

সারসংক্ষেপ

  • মেমরি লিক — অবজেক্ট কোডের কাছে অপ্রাপ্য কিন্তু GC দ্বারা অপসারিত হয়নি কারণ GC Root থেকে একটি সক্রিয় রেফারেন্স বিদ্যমান
  • GC Roots-এ স্ট্যাটিক ফিল্ড, স্ট্যাক ভেরিয়েবল এবং JNI রেফারেন্স অন্তর্ভুক্ত; তাদের থেকে পৌঁছানো যায় এমন যেকোনো অবজেক্ট জীবিত
  • Context লিক — Android-এ সবচেয়ে বিস্তৃত সমস্যা: সিঙ্গলটন বা স্ট্যাটিক ফিল্ডে Activity Context পাস করা
  • Handler এবং অভ্যন্তরীণ ক্লাস — দ্বিতীয় সবচেয়ে সাধারণ কারণ: Looper সারিতে বাতিল না করা বার্তা Activity-র রেফারেন্স রাখে
  • LeakCanary — মানক স্বয়ংক্রিয় সনাক্তকরণ সরঞ্জাম; heap dump নেয় এবং সঠিক GC root chain দেখায়
  • lifecycleScope এবং viewModelScope করুটিনের মাধ্যমে লিকের সমস্যা সমাধান করে — ধ্বংসের সময় স্বয়ংক্রিয় বাতিলকরণ
  • CI/CD-তে মেমরি প্রোফাইল করুন: ডিবাগে LeakCanary + পরীক্ষা রানে heap dump বিশ্লেষণ নতুন লিকে মার্জ ব্লক করা উচিত

আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব

IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।

প্রকল্প নিয়ে আলোচনা করুন

আরও পড়ুন