OutOfMemoryError در توسعه برنامه‌ها: چیست، علل و روش‌های پیشگیری

نویسنده: IT Sectr منتشر شده: 2026-03-29 زمان مطالعه: 9 دقیقه

OutOfMemoryError — یک استثنای مرگبار است که زمانی رخ می‌دهد که ماشین مجازی جاوا (JVM) یا Android Runtime (ART) نمی‌تواند حافظه را برای یک شیء جدید به دلیل کمبود فضای heap اختصاص دهد. به گفته Square Engineering، 70٪ از خطاهای OutOfMemoryError در برنامه‌های موبایل ناشی از نشت حافظه است، نه تجاوز واقعی از حد مجاز. درک علل OOM کلید عملکرد پایدار برنامه است.

نکات اصلی

  • OutOfMemoryError — استثنا هنگام کمبود Heap برای ایجاد شیء جدید
  • Heap (پشته) — ناحیه حافظه‌ای که همه اشیاء Java/Kotlin در آن زندگی می‌کنند
  • Bitmap — مصرف‌کننده اصلی Heap در اندروید، منبع معمول OOM
  • Heap Dump — عکس‌برداری از پشته برای تحلیل اینکه چه کسی چقدر حافظه مصرف می‌کند
  • درمان OOM نیازمند رفع نشت‌ها و بهینه‌سازی مصرف حافظه است

OutOfMemoryError چیست

OutOfMemoryError (OOM) — استثنایی از خانواده VirtualMachineError در Java/Kotlin است که عدم امکان تخصیص حافظه برای یک شیء جدید را نشان می‌دهد. برخلاف استثناهای checked، OOM یک Error است و نیازی به مدیریت از طریق catch ندارد — اگرچه از نظر فنی می‌توان آن را گرفت. پس از وقوع OOM، برنامه معمولاً در وضعیت ناپایداری قرار دارد و توصیه می‌شود آن را خاتمه دهید.

در اندروید، هر برنامه دارای محدودیت Heap تعیین‌شده توسط سازنده دستگاه است. برای گوشی‌های هوشمند مدرن با 6+ گیگابایت رم، محدودیت 256–512 مگابایت است، برای دستگاه‌های اقتصادی — 128–192 مگابایت. هنگامی که حجم کل همه اشیاء زنده از این محدودیت فراتر رود، ART خطای OutOfMemoryError را صادر می‌کند.

مهم است بدانیم: OOM همیشه به معنای اتمام حافظه فیزیکی دستگاه نیست. به این معنی است که برنامه محدودیت Heap تعیین‌شده توسط سیستم را تمام کرده است. سایر برنامه‌ها ممکن است حافظه آزاد داشته باشند، اما برنامه شما به دلیل ایزوله‌سازی فرآیندها در اندروید نمی‌تواند از آن استفاده کند.

علل اصلی OutOfMemoryError

پنج سناریو به طور منظم منجر به OOM در برنامه‌های موبایل می‌شوند. هر سناریو با نوع خاصی از داده یا عملیات مرتبط است.

Bitmap بدون مقیاس‌بندی

Bitmap — مصرف‌کننده اصلی حافظه در برنامه‌های اندروید. بارگذاری تصویر FullHD (1920 × 1080) در اندازه اصلی 8.3 مگابایت در فرمت ARGB_8888 اشغال می‌کند. اگر 50 تصویر از این نوع در RecyclerView وجود داشته باشد — این 415 مگابایت است که از Heap هر دستگاهی فراتر می‌رود. بارگذاری تصاویر بدون inSampleSize — OOM تضمینی در دستگاه‌های ضعیف.

از Glide یا Coil برای مقیاس‌بندی خودکار استفاده کنید. این کتابخانه‌ها تصاویر را در اندازه‌ای متناسب با View بارگذاری می‌کنند، نه رزولوشن اصلی. برای استفاده مستقیم از BitmapFactory.Options از inSampleSize استفاده کنید: آن را به عنوان توانی از دو محاسبه کنید تا اندازه نهایی از 2048 × 2048 پیکسل تجاوز نکند. همچنین برای تصاویر بدون شفافیت از RGB_565 به جای ARGB_8888 استفاده کنید — این میزان مصرف حافظه را نصف می‌کند.

kotlin
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)
}

نشت حافظه (انباشتگی)

یک نشت چند کیلوبایتی باعث OOM نمی‌شود. اما ده‌ها نشت در هر صفحه انباشته می‌شوند: هر بار که به صفحه‌ای می‌روید، یک نشت اضافه می‌شود، GC نمی‌تواند اشیاء را آزاد کند، Heap پر می‌شود. الگوی معمول: کاربر 20 بار صفحه پروفایل را باز و بسته می‌کند → Heap 200 مگابایت رشد می‌کند → برنامه با OOM سقوط می‌کند.

LeakCanary را در پروژه برای تشخیص خودکار نشت‌ها نصب کنید. این ابزار هر شیء نشت‌یافته را با stack trace دقیق نشان می‌دهد. پس از رفع همه نشت‌ها، مصرف Heap پایدار می‌شود: پس از بسته شدن صفحه، حافظه به سطح پایه بازمی‌گردد.

فایل‌های حجیم در حافظه

بارگذاری کامل فایل‌ها در byte[] — مسیر مستقیم به OOM. یک فایل JSON 50 مگابایتی هنگام تجزیه، رشته‌ای به همان اندازه به علاوه مدل DOM ایجاد می‌کند. فایل‌های ویدئویی بارگذاری‌شده در حافظه، بافرهای صوتی و مجموعه‌داده‌های بزرگ protobuf — همه می‌توانند محدودیت Heap را در یک عملیات رد کنند.

داده‌های بزرگ را با جریان پردازش کنید: InputStream با بافر 4–8 کیلوبایت، تجزیه‌کننده JSON جریانی (Jackson یا Gson با JsonReader)، MediaCodec برای ویدئو. هرگز File.readBytes() را روی فایل‌های بزرگتر از 10٪ Heap موجود فراخوانی نکنید.

ایجاد اشیاء زیاد در حلقه

ایجاد فشرده اشیاء در حلقه بدون GC میانی می‌تواند منجر به OOM شود، به‌ویژه در دستگاه‌های با Heap کوچک. مثال: ایجاد 100,000 شیء در حلقه for که در Heap جا نمی‌شوند قبل از اینکه GC فرصت جمع‌آوری آنها را داشته باشد. این بیشتر در بازی‌ها و ویرایشگرهای گرافیکی رایج است.

برای اشیایی که به صورت انبوه ایجاد و نابود می‌شوند از Object Pool استفاده کنید. برای داده‌های عددی از انواع اولیه استفاده کنید (FloatArray به جای List<Float>). RecyclerView با ViewHolder Pool این مشکل را برای کامپوننت‌های UI حل می‌کند.

تکه‌تکه شدن Heap

تکه‌تکه شدن — وضعیتی است که در آن حافظه آزاد به طور کلی کافی است، اما بلوک پیوسته‌ای برای شیء جدید وجود ندارد. ART در هنگام GC، Heap را فشرده می‌کند، اما همیشه با موفقیت نیست. آرایه‌های بزرگ (Bitmap، byte[]) بیشترین حساسیت را به تکه‌تکه شدن دارند.

ART در اندروید 8+ از Generational GC استفاده می‌کند که با جداسازی اشیاء جوان و پیر، تکه‌تکه شدن را کاهش می‌دهد. با این حال، از تخصیص تکه‌هایی با اندازه‌های مختلف در یک استخر خودداری کنید — سعی کنید از بافرهای از پیش تخصیص‌یافته با اندازه ثابت استفاده کنید.

محدودیت‌های Heap در اندروید

محدودیت Heap در اندروید ثابت نیست — به سازنده، مدل دستگاه و نسخه سیستم‌عامل بستگی دارد. گوگل حداقل نیازها را از طریق Compatibility Definition Document (CDD) تعیین می‌کند، اما سازندگان مقادیر واقعی را تنظیم می‌کنند.

دسته‌بندی دستگاه‌هاHeap معمولlargeHeap
اقتصادی (1–2 GB RAM)128–192 MB256–384 MB
میان‌رده (3–4 GB RAM)256–384 MB512 MB
پرچمدار (6+ GB RAM)384–512 MB768 MB–1 GB
تبلت‌ها (4+ GB RAM)256–512 MB768 MB
Wear OS32–64 MBندارد

می‌توانید محدودیت افزایش‌یافته را از طریق android:largeHeap="true" در مانیفست درخواست کنید. با احتیاط از آن استفاده کنید: افزایش Heap مشکل نشت‌ها را حل نمی‌کند و اگر سیستم مجبور شود سایر برنامه‌ها را برای آزادسازی حافظه از بین ببرد، می‌تواند تجربه کاربری را بدتر کند. برای Wear OS محدودیت Heap حداقل است — فقط 32–64 MB، در اینجا largeHeap در دسترس نیست و صرفه‌جویی در حافظه دوچندان حیاتی است.

تشخیص OutOfMemoryError

تشخیص OOM نیاز به تحلیل Heap Dump و درک اینکه کدام اشیاء حافظه مصرف می‌کنند دارد. Android Studio تمام ابزارهای لازم را فراهم می‌کند.

مرحله 1: لحظه OOM را بگیرید. در Android Memory Profiler روی Record memory allocations کلیک کنید و سناریویی که باعث سقوط می‌شود را اجرا کنید. Profiler جهش تخصیص‌ها را قبل از OOM نشان می‌دهد. اگر OOM تکرار نمی‌شود، Heap را از طریق android:smallHeap در بیلد دیباگ کاهش دهید یا از DDMS با فراخوانی دستی GC استفاده کنید.

مرحله 2: در زمان اوج بار (قبل از OOM) Heap Dump بگیرید. Dump را در Android Studio باز کنید: برگه Classes بر اساس Retained Size مرتب شده است. بزرگترین اشیاء — Bitmap، byte[]، String. برای هر Bitmap اندازه (width × height × 4 بایت) و مسیر بارگذاری را از طریق Stack Trace بررسی کنید.

مرحله 3: تعداد اشیاء تکراری را تحلیل کنید. اگر 200 Fragment یا Activity یکسان می‌بینید — این نشت است. اگر 500 Bitmap با اندازه یکسان — مشکل کش تصاویر. MAT (Memory Analyzer Tool) تحلیل عمیق‌تری با Dominator Tree ارائه می‌دهد که نشان می‌دهد کدام اشیاء 80٪ Heap را نگه داشته‌اند.

text
// دستور Heap Dump از طریق adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.

راهکارهای پیشگیری از OOM

راهکار جامع پیشگیری از OOM شامل پنج سطح محافظت است: از راه‌حل‌های معماری تا نظارت در محیط تولید.

راه‌حل‌های معماری

ViewModel + Repository الگویی است که داده‌ها را از UI جدا می‌کند و از نگهداری View هنگام چرخش صفحه جلوگیری می‌کند. ViewModel بیشتر از Activity عمر می‌کند، داده‌های آن از بین نمی‌روند و View می‌تواند بدون تکرار داده‌ها در حافظه بازسازی شود. برای مدیریت صریح حالات از StateFlow به جای LiveData استفاده کنید.

مدیریت Bitmap و تصاویر

Glide — کتابخانه الزامی برای کار با تصاویر است. این کتابخانه به طور خودکار مقیاس‌بندی، کش (دیسک + حافظه) و بازیافت Bitmap را انجام می‌دهد. diskCacheStrategy و skipMemoryCache را برای لیست‌های بزرگ پیکربندی کنید. برای تصاویر متحرک از Glide با GIF/WebP استفاده کنید — آنها حافظه کمتری نسبت به دنباله Bitmap اشغال می‌کنند.

نظارت در محیط تولید

Firebase Performance Monitoring مصرف حافظه را در زمان واقعی ردیابی می‌کند. برای اشغال Heap بیش از 80٪ از حد مجاز هشدار تنظیم کنید — این سیگنالی برای بررسی است. Crashlytics OOM را به عنوان یک استثنا جمع‌آوری می‌کند و آخرین وضعیت Heap را قبل از سقوط نشان می‌دهد. برای اندروید 11+ از ApplicationExitInfo برای تشخیص خاتمه‌های OOM استفاده کنید.

تست روی دستگاه‌های ضعیف

الزاماً برنامه را روی دستگاه‌هایی با حداقل Heap (128–192 MB) تست کنید. شبیه‌ساز با صفحه کوچک و Heap کوچک، دستگاه اقتصادی را شبیه‌سازی می‌کند. اگر برنامه روی چنین دستگاهی کار کند، روی پرچمداران مشکل OOM نخواهد بود. از Firebase Test Lab با دستگاه‌های واقعی از رده‌های قیمتی مختلف استفاده کنید.

kotlin
// بررسی Heap موجود قبل از عملیات سنگین
fun canAllocate(requiredBytes: Long): Boolean {
    val runtime = Runtime.getRuntime()
    val free = runtime.freeMemory()
    return free > requiredBytes * 2 // ذخیره 50%
}

سوالات متداول

آیا می‌توان OutOfMemoryError را از طریق try-catch گرفت؟

از نظر فنی بله، اما انجام این کار توصیه نمی‌شود. پس از OOM، برنامه در وضعیت ناپایداری قرار دارد: تخصیص‌های جدید ممکن است کار نکنند و برخی اشیاء ممکن است نیمه ایجاد شده باشند. تنها اقدام منطقی در catch — ثبت لاگ و راه‌اندازی مجدد Activity است.

چرا OOM در همه دستگاه‌ها رخ نمی‌دهد؟

محدودیت Heap در دستگاه‌های مختلف متفاوت است. عملیاتی که به 300 MB نیاز دارد، در دستگاهی با محدودیت 192 MB سقوط می‌کند، اما در پرچمداری با 512 MB عبور می‌کند. برای کشف سناریوهای OOM روی دستگاه‌هایی با حداقل مشخصات تست کنید.

largeHeap چگونه بر عملکرد تأثیر می‌گذارد؟

largeHeap محدودیت را افزایش می‌دهد، اما برنامه را سریع‌تر نمی‌کند. مکث‌های GC طولانی‌تر می‌شوند، زیرا جمع‌آوری Heap بزرگ بیشتر طول می‌کشد. سیستم ممکن است برنامه‌های پس‌زمینه را برای تأمین حافظه از بین ببرد. largeHeap را فقط برای برنامه‌هایی استفاده کنید که واقعاً به حافظه زیادی نیاز دارند (دوربین‌ها، ویرایشگرها).

تفاوت OOM با کشتن سیستم‌عامل فرآیند چیست؟

OOM — استثنایی در داخل برنامه هنگام کمبود Heap است. کشتن سیستم‌عامل (Low Memory Killer) — تصمیم هسته لینوکس برای کشتن یک فرآیند به منظور آزادسازی حافظه برای برنامه‌های دیگر است. در کشتن سیستم‌عامل، برنامه استثنا دریافت نمی‌کند — فرآیند به سادگی خاتمه می‌یابد.

Bitmap واقعاً چقدر حافظه اشغال می‌کند؟

فرمول: width × height × bytesPerPixel. ARGB_8888 = 4 بایت/پیکسل، RGB_565 = 2 بایت/پیکسل. Bitmap FullHD (1920 × 1080) در ARGB_8888 = 8.3 MB. Bitmap 4K (3840 × 2160) = 33 MB. همیشه تصاویر را به اندازه لازم برای نمایش روی صفحه مقیاس کنید.

خلاصه

  • OutOfMemoryError — استثنای مرگبار هنگام اتمام محدودیت Heap برنامه
  • Bitmap بدون مقیاس‌بندی — عامل اصلی OOM در برنامه‌های موبایل
  • نشت حافظه با انباشت اشیاء در هر بار پیمایش باعث 70٪ OOM می‌شود
  • محدودیت Heap از 128 MB در دستگاه‌های اقتصادی تا 512 MB در پرچمداران متغیر است
  • Heap Dump با تحلیل Retained Size — ابزار اصلی تشخیص OOM
  • Glide یا Coil برای کار با تصاویر با هر اندازه الزامی است
  • تست روی دستگاه‌هایی با حداقل Heap — must-have برای همه پروژه‌ها

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید