OutOfMemoryError একটি মারাত্মক ব্যতিক্রম যা ঘটে যখন Java ভার্চুয়াল মেশিন (JVM) বা Android Runtime (ART) Heap-এ পর্যাপ্ত স্থানের অভাবে একটি নতুন অবজেক্টের জন্য মেমোরি বরাদ্দ করতে পারে না। Square Engineering-এর মতে, মোবাইল অ্যাপ্লিকেশনে 70% OutOfMemoryError মেমোরি লিকের কারণে ঘটে, প্রকৃত সীমা অতিক্রম করার কারণে নয়। OOM-এর কারণ বোঝা অ্যাপ্লিকেশনের স্থিতিশীলতার চাবিকাঠি।
মূল বিষয়
OutOfMemoryError (OOM) Java/Kotlin-এ VirtualMachineError পরিবারের একটি ব্যতিক্রম যা একটি নতুন অবজেক্টের জন্য মেমোরি বরাদ্দ করতে অক্ষমতার ইঙ্গিত দেয়। চেক করা ব্যতিক্রমের বিপরীতে, OOM একটি Error এবং catch-এর মাধ্যমে হ্যান্ডলিং প্রয়োজন হয় না — যদিও এটি প্রযুক্তিগতভাবে ধরা যেতে পারে। OOM ঘটার পর, অ্যাপ্লিকেশন সাধারণত অস্থিতিশীল অবস্থায় থাকে এবং এটি শেষ করার সুপারিশ করা হয়।
Android-এ, প্রতিটি অ্যাপ্লিকেশনের একটি Heap সীমা থাকে যা ডিভাইস প্রস্তুতকারক নির্ধারণ করে। 6+ জিবি RAM বিশিষ্ট আধুনিক স্মার্টফোনের জন্য, সীমা 256–512 এমবি, বাজেট ডিভাইসের জন্য — 128–192 এমবি। যখন সমস্ত জীবিত অবজেক্টের মোট আয়তন এই সীমা অতিক্রম করে, তখন ART OutOfMemoryError ছোড়ে।
এটি বোঝা গুরুত্বপূর্ণ: OOM সবসময় মানে না যে ডিভাইসের ফিজিক্যাল মেমোরি শেষ হয়ে গেছে। এর মানে হল অ্যাপ্লিকেশনটি সিস্টেম দ্বারা নির্ধারিত তার Heap সীমা শেষ করে দিয়েছে। অন্যান্য অ্যাপ্লিকেশনের ফাঁকা মেমোরি থাকতে পারে, কিন্তু Android-এ প্রক্রিয়া বিচ্ছিন্নতার কারণে আপনার অ্যাপ্লিকেশন এটি ব্যবহার করতে পারে না।
পাঁচটি পরিস্থিতি নিয়মিতভাবে মোবাইল অ্যাপ্লিকেশনে OOM ঘটায়। প্রতিটি পরিস্থিতি একটি নির্দিষ্ট ডেটা প্রকার বা অপারেশনের সাথে সম্পর্কিত।
Bitmap Android অ্যাপ্লিকেশনে প্রধান মেমোরি ভোক্তা। ARGB_8888 ফরম্যাটে একটি FullHD ছবি (1920 × 1080) মূল আকারে লোড করতে 8.3 এমবি লাগে। যদি RecyclerView-এ এরকম 50টি ছবি থাকে — তাহলে তা 415 এমবি, যা যেকোনো ডিভাইসের Heap-এর চেয়ে বেশি। inSampleSize ছাড়া ছবি লোড করা দুর্বল ডিভাইসে নিশ্চিত OOM।
স্বয়ংক্রিয় স্কেলিংয়ের জন্য Glide বা Coil ব্যবহার করুন। এই লাইব্রেরিগুলো মূল রেজোলিউশনের পরিবর্তে View-এর সাথে মেলে এমন আকারে ছবি লোড করে। BitmapFactory.Options-এর সরাসরি ব্যবহারের জন্য inSampleSize প্রয়োগ করুন: এটি দুইয়ের ঘাত হিসেবে গণনা করুন যাতে চূড়ান্ত আকার 2048 × 2048 পিক্সেলের বেশি না হয়। অতিরিক্তভাবে, স্বচ্ছতা ছাড়া ছবির জন্য ARGB_8888-এর পরিবর্তে RGB_565 ব্যবহার করুন — এটি মেমোরি খরচ অর্ধেক কমিয়ে দেয়।
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
একটি কয়েক KB-র লিক OOM ঘটাবে না। কিন্তু প্রতিটি স্ক্রিনে ডজনখানেক লিক জমা হয়: প্রতিটি স্ক্রিন ট্রানজিশন একটি লিক যোগ করে, GC অবজেক্ট মুক্ত করতে পারে না, এবং Heap ভরে যায়। একটি সাধারণ প্যাটার্ন: ব্যবহারকারী প্রোফাইল স্ক্রিন 20 বার খোলে এবং বন্ধ করে → Heap 200 এমবি বাড়ে → অ্যাপ্লিকেশন OOM-এর সাথে ক্র্যাশ হয়।
স্বয়ংক্রিয় লিক সনাক্তকরণের জন্য প্রকল্পে LeakCanary ইনস্টল করুন। এটি সঠিক স্ট্যাক ট্রেস সহ প্রতিটি লিক করা অবজেক্ট দেখাবে। সমস্ত লিক ঠিক করার পর, Heap খরচ স্থিতিশীল হয়: একটি স্ক্রিন বন্ধ করার পর, মেমোরি বেস লেভেলে ফিরে আসে।
সম্পূর্ণ ফাইল byte[]-তে লোড করা OOM-এর সরাসরি পথ। 50 এমবি-র JSON ফাইল পার্স করার সময় একই আকারের একটি স্ট্রিং এবং একটি DOM মডেল তৈরি করবে। মেমোরিতে লোড করা ভিডিও ফাইল, অডিও বাফার এবং বড় protobuf ডেটাসেট — এগুলি সবই একটি একক অপারেশনে Heap সীমা অতিক্রম করতে পারে।
স্ট্রিম ব্যবহার করে বড় ডেটা প্রক্রিয়া করুন: 4–8 KB বাফার সহ InputStream, স্ট্রিমিং JSON পার্সার (Jackson বা Gson JsonReader সহ), ভিডিওর জন্য MediaCodec। উপলব্ধ Heap-এর 10%-এর বেশি আকারের ফাইলে কখনও File.readBytes() কল করবেন না।
লুপে মধ্যবর্তী GC ছাড়া গভীর অবজেক্ট তৈরি OOM ঘটাতে পারে, বিশেষ করে ছোট Heap বিশিষ্ট ডিভাইসে। উদাহরণ: for-loop-এ 100,000 অবজেক্ট তৈরি করা যা GC সংগ্রহের আগে Heap-এ ধরে না। এটি গেম এবং গ্রাফিক্স এডিটরে বেশি সাধারণ।
যে অবজেক্টগুলো大量ভরে তৈরি এবং ধ্বংস হয় তাদের জন্য Object Pool ব্যবহার করুন। সংখ্যাসূচক ডেটার জন্য প্রিমিটিভ ব্যবহার করুন (List<Float>-এর পরিবর্তে FloatArray)। ViewHolder Pool সহ RecyclerView UI কম্পোনেন্টের জন্য এই সমস্যা সমাধান করে।
খণ্ডিতকরণ এমন একটি অবস্থা যেখানে মোটামুটিভাবে পর্যাপ্ত ফাঁকা মেমোরি আছে, কিন্তু একটি নতুন অবজেক্টের জন্য কোনো সংলগ্ন ব্লক নেই। ART GC-এর সময় Heap সংকুচিত করে, কিন্তু সবসময় সফলভাবে নয়। বড় অ্যারে (Bitmap, byte[]) খণ্ডিতকরণের জন্য সবচেয়ে সংবেদনশীল।
Android 8+-এ ART Generational GC ব্যবহার করে, যা তরুণ এবং পুরানো অবজেক্ট আলাদা করে খণ্ডিতকরণ কমায়। তবুও, একই পুলে বিভিন্ন আকারের টুকরা বরাদ্দ করা এড়িয়ে চলুন — পূর্ব-বরাদ্দকৃত নির্দিষ্ট আকারের বাফার ব্যবহার করার চেষ্টা করুন।
Android-এ Heap সীমা একটি ধ্রুবক নয় — এটি প্রস্তুতকারক, ডিভাইস মডেল এবং OS সংস্করণের উপর নির্ভর করে। Google Compatibility Definition Document (CDD)-এর মাধ্যমে ন্যূনতম প্রয়োজনীয়তা নির্ধারণ করে, কিন্তু প্রস্তুতকারকরা প্রকৃত মান নির্ধারণ করে।
| ডিভাইস ক্যাটাগরি | সাধারণ Heap | largeHeap |
|---|---|---|
| বাজেট (1–2 জিবি RAM) | 128–192 এমবি | 256–384 এমবি |
| মিড-রেঞ্জ (3–4 জিবি RAM) | 256–384 এমবি | 512 এমবি |
| ফ্ল্যাগশিপ (6+ জিবি RAM) | 384–512 এমবি | 768 এমবি–1 জিবি |
| ট্যাবলেট (4+ জিবি RAM) | 256–512 এমবি | 768 এমবি |
| Wear OS | 32–64 এমবি | উপলব্ধ নয় |
আপনি ম্যানিফেস্টে android:largeHeap="true"-এর মাধ্যমে বর্ধিত সীমার অনুরোধ করতে পারেন। সতর্কতার সাথে ব্যবহার করুন: Heap বাড়ানো লিকের সমস্যা সমাধান করে না এবং ব্যবহারকারীর অভিজ্ঞতা খারাপ করতে পারে যদি সিস্টেম আপনার অ্যাপ্লিকেশনের জন্য মেমোরি মুক্ত করতে অন্যান্য অ্যাপ্লিকেশন বন্ধ করতে বাধ্য হয়। Wear OS-এর জন্য, Heap সীমা ন্যূনতম — মাত্র 32–64 এমবি, এখানে largeHeap উপলব্ধ নয়, এবং মেমোরি সাশ্রয় দ্বিগুণ গুরুত্বপূর্ণ।
OOM নির্ণয়ের জন্য Heap Dump বিশ্লেষণ এবং কোন অবজেক্ট মেমোরি খরচ করছে তা বোঝা প্রয়োজন। Android Studio সমস্ত প্রয়োজনীয় টুল সরবরাহ করে।
ধাপ 1: OOM মুহূর্ত ক্যাপচার করুন। Android Memory Profiler-এ, Record memory allocations-এ ক্লিক করুন এবং যে পরিস্থিতি ক্র্যাশ ঘটায় তা সম্পাদন করুন। Profiler OOM-এর আগে বরাদ্দের বৃদ্ধি দেখাবে। যদি OOM পুনরুৎপাদনযোগ্য না হয়, তবে ডিবাগ বিল্ডে android:smallHeap-এর মাধ্যমে Heap কমিয়ে দিন বা ম্যানুয়াল GC কল সহ DDMS ব্যবহার করুন।
ধাপ 2: পিক লোডে (OOM-এর আগে) Heap Dump নিন। Android Studio-তে Dump খুলুন: Classes ট্যাব Retained Size অনুযায়ী সাজানো। সবচেয়ে বড় অবজেক্টগুলো হলো Bitmap, byte[], String। প্রতিটি Bitmap-এর জন্য, আকার (প্রস্থ × উচ্চতা × 4 বাইট) এবং Stack Trace-এর মাধ্যমে লোড পাথ দেখুন।
ধাপ 3: ডুপ্লিকেট অবজেক্টের সংখ্যা বিশ্লেষণ করুন। যদি আপনি 200টি অভিন্ন Fragment বা Activity দেখতে পান — এটি একটি লিক। যদি একই আকারের 500টি Bitmap — এটি একটি ছবি ক্যাশিং সমস্যা। MAT (Memory Analyzer Tool) Dominator Tree-সহ গভীর বিশ্লেষণ প্রদান করে যা দেখায় কোন অবজেক্ট 80% Heap ধরে রেখেছে।
// adb-এর মাধ্যমে Heap Dump কমান্ড
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
একটি ব্যাপক OOM প্রতিরোধ কৌশলে সুরক্ষার পাঁচটি স্তর অন্তর্ভুক্ত: আর্কিটেকচারাল সিদ্ধান্ত থেকে প্রোডাকশন মনিটরিং পর্যন্ত।
ViewModel + Repository প্যাটার্ন ডেটাকে UI থেকে আলাদা করে এবং স্ক্রিন রোটেশনে View ধরে রাখা প্রতিরোধ করে। ViewModel Activity-র চেয়ে বেশি বাঁচে, এর ডেটা হারায় না, এবং View-কে মেমোরিতে ডেটা ডুপ্লিকেট না করে পুনরায় তৈরি করা যায়। স্পষ্ট অবস্থা পরিচালনার জন্য LiveData-এর পরিবর্তে StateFlow ব্যবহার করুন।
Glide ছবির সাথে কাজ করার জন্য একটি বাধ্যতামূলক লাইব্রেরি। এটি স্বয়ংক্রিয়ভাবে স্কেল, ক্যাশ (ডিস্ক + মেমোরি) এবং Bitmap রিসাইকেল করে। বড় তালিকার জন্য diskCacheStrategy এবং skipMemoryCache কনফিগার করুন। অ্যানিমেটেড ছবির জন্য, GIF/WebP-সহ Glide ব্যবহার করুন — এগুলো Bitmap-এর ক্রমের চেয়ে কম মেমোরি নেয়।
Firebase Performance Monitoring রিয়েল টাইমে মেমোরি খরচ ট্র্যাক করে। Heap ব্যবহার 80% সীমা অতিক্রম করলে একটি সতর্কতা সেট করুন — এটি পরীক্ষার সংকেত। Crashlytics OOM-কে একটি ব্যতিক্রম হিসেবে সংগ্রহ করে এবং ক্র্যাশের আগে শেষ জানা Heap অবস্থা দেখায়। Android 11+-এর জন্য, OOM সমাপ্তি সনাক্ত করতে ApplicationExitInfo ব্যবহার করুন।
নিশ্চিত করুন যে আপনি ন্যূনতম Heap (128–192 এমবি) বিশিষ্ট ডিভাইসে অ্যাপ্লিকেশন পরীক্ষা করেন। ছোট স্ক্রিন এবং ছোট Heap-সহ এমুলেটর একটি বাজেট ডিভাইস অনুকরণ করে। যদি অ্যাপ্লিকেশন এই জাতীয় ডিভাইসে কাজ করে, তবে ফ্ল্যাগশিপে OOM সমস্যা হবে না। বিভিন্ন মূল্য শ্রেণীর বাস্তব ডিভাইস-সহ Firebase Test Lab ব্যবহার করুন।
// ভারী অপারেশনের আগে উপলব্ধ Heap পরীক্ষা
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // 50% বাফার
}
সচরাচর জিজ্ঞাসিত প্রশ্ন
প্রযুক্তিগতভাবে হ্যাঁ, কিন্তু এটি সুপারিশ করা হয় না। OOM-এর পর, অ্যাপ্লিকেশন অস্থিতিশীল অবস্থায় থাকে: নতুন বরাদ্দ ব্যর্থ হতে পারে, এবং কিছু অবজেক্ট আংশিকভাবে তৈরি হতে পারে। catch-এ একমাত্র যুক্তিসঙ্গত কাজ হল লগিং এবং Activity পুনরায় চালু করা।
Heap সীমা ডিভাইসগুলির মধ্যে ভিন্ন হয়। একটি অপারেশন যার 300 এমবি প্রয়োজন, তা 192 এমবি সীমা বিশিষ্ট ডিভাইসে ব্যর্থ হবে কিন্তু 512 এমবি বিশিষ্ট ফ্ল্যাগশিপে সফল হবে। OOM পরিস্থিতি সনাক্ত করতে ন্যূনতম স্পেসিফিকেশনযুক্ত ডিভাইসে পরীক্ষা করুন।
largeHeap সীমা বাড়ায় কিন্তু অ্যাপ্লিকেশনকে গতি দেয় না। GC বিরতি দীর্ঘ হয় কারণ বড় Heap সংগ্রহ করতে বেশি সময় লাগে। সিস্টেম মেমোরি সরবরাহ করতে পটভূমির অ্যাপ্লিকেশন বন্ধ করতে পারে। largeHeap শুধুমাত্র সেই অ্যাপ্লিকেশনের জন্য ব্যবহার করুন যাদের বস্তুনিষ্ঠভাবে প্রচুর মেমোরি প্রয়োজন (ক্যামেরা, এডিটর)।
OOM Heap অপর্যাপ্ত হলে অ্যাপ্লিকেশনের ভিতরে একটি ব্যতিক্রম। সিস্টেম কিল (Low Memory Killer) হল Linux কার্নেলের সিদ্ধান্ত অন্যান্য অ্যাপ্লিকেশনের জন্য মেমোরি মুক্ত করতে একটি প্রক্রিয়া বন্ধ করার। সিস্টেম কিলে, অ্যাপ্লিকেশন কোনো ব্যতিক্রম পায় না — প্রক্রিয়াটি কেবল শেষ হয়ে যায়।
সূত্র: প্রস্থ × উচ্চতা × bytesPerPixel। ARGB_8888 = 4 বাইট/পিক্সেল, RGB_565 = 2 বাইট/পিক্সেল। ARGB_8888-এ FullHD Bitmap (1920 × 1080) = 8.3 এমবি। 4K Bitmap (3840 × 2160) = 33 এমবি। ছবিগুলো সবসময় স্ক্রিনে প্রদর্শনের জন্য প্রয়োজনীয় আকারে স্কেল করুন।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন