OutOfMemoryError — یک استثنای مرگبار است که زمانی رخ میدهد که ماشین مجازی جاوا (JVM) یا Android Runtime (ART) نمیتواند حافظه را برای یک شیء جدید به دلیل کمبود فضای heap اختصاص دهد. به گفته Square Engineering، 70٪ از خطاهای OutOfMemoryError در برنامههای موبایل ناشی از نشت حافظه است، نه تجاوز واقعی از حد مجاز. درک علل OOM کلید عملکرد پایدار برنامه است.
نکات اصلی
OutOfMemoryError (OOM) — استثنایی از خانواده VirtualMachineError در Java/Kotlin است که عدم امکان تخصیص حافظه برای یک شیء جدید را نشان میدهد. برخلاف استثناهای checked، OOM یک Error است و نیازی به مدیریت از طریق catch ندارد — اگرچه از نظر فنی میتوان آن را گرفت. پس از وقوع OOM، برنامه معمولاً در وضعیت ناپایداری قرار دارد و توصیه میشود آن را خاتمه دهید.
در اندروید، هر برنامه دارای محدودیت Heap تعیینشده توسط سازنده دستگاه است. برای گوشیهای هوشمند مدرن با 6+ گیگابایت رم، محدودیت 256–512 مگابایت است، برای دستگاههای اقتصادی — 128–192 مگابایت. هنگامی که حجم کل همه اشیاء زنده از این محدودیت فراتر رود، ART خطای OutOfMemoryError را صادر میکند.
مهم است بدانیم: OOM همیشه به معنای اتمام حافظه فیزیکی دستگاه نیست. به این معنی است که برنامه محدودیت Heap تعیینشده توسط سیستم را تمام کرده است. سایر برنامهها ممکن است حافظه آزاد داشته باشند، اما برنامه شما به دلیل ایزولهسازی فرآیندها در اندروید نمیتواند از آن استفاده کند.
پنج سناریو به طور منظم منجر به OOM در برنامههای موبایل میشوند. هر سناریو با نوع خاصی از داده یا عملیات مرتبط است.
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 استفاده کنید — این میزان مصرف حافظه را نصف میکند.
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 حل میکند.
تکهتکه شدن — وضعیتی است که در آن حافظه آزاد به طور کلی کافی است، اما بلوک پیوستهای برای شیء جدید وجود ندارد. ART در هنگام GC، Heap را فشرده میکند، اما همیشه با موفقیت نیست. آرایههای بزرگ (Bitmap، byte[]) بیشترین حساسیت را به تکهتکه شدن دارند.
ART در اندروید 8+ از Generational GC استفاده میکند که با جداسازی اشیاء جوان و پیر، تکهتکه شدن را کاهش میدهد. با این حال، از تخصیص تکههایی با اندازههای مختلف در یک استخر خودداری کنید — سعی کنید از بافرهای از پیش تخصیصیافته با اندازه ثابت استفاده کنید.
محدودیت Heap در اندروید ثابت نیست — به سازنده، مدل دستگاه و نسخه سیستمعامل بستگی دارد. گوگل حداقل نیازها را از طریق Compatibility Definition Document (CDD) تعیین میکند، اما سازندگان مقادیر واقعی را تنظیم میکنند.
| دستهبندی دستگاهها | Heap معمول | largeHeap |
|---|---|---|
| اقتصادی (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| میانرده (3–4 GB RAM) | 256–384 MB | 512 MB |
| پرچمدار (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| تبلتها (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | ندارد |
میتوانید محدودیت افزایشیافته را از طریق android:largeHeap="true" در مانیفست درخواست کنید. با احتیاط از آن استفاده کنید: افزایش Heap مشکل نشتها را حل نمیکند و اگر سیستم مجبور شود سایر برنامهها را برای آزادسازی حافظه از بین ببرد، میتواند تجربه کاربری را بدتر کند. برای Wear OS محدودیت Heap حداقل است — فقط 32–64 MB، در اینجا largeHeap در دسترس نیست و صرفهجویی در حافظه دوچندان حیاتی است.
تشخیص 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 را نگه داشتهاند.
// دستور Heap Dump از طریق adb
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 میتواند بدون تکرار دادهها در حافظه بازسازی شود. برای مدیریت صریح حالات از StateFlow به جای LiveData استفاده کنید.
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 با دستگاههای واقعی از ردههای قیمتی مختلف استفاده کنید.
// بررسی Heap موجود قبل از عملیات سنگین
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // ذخیره 50%
}
سوالات متداول
از نظر فنی بله، اما انجام این کار توصیه نمیشود. پس از OOM، برنامه در وضعیت ناپایداری قرار دارد: تخصیصهای جدید ممکن است کار نکنند و برخی اشیاء ممکن است نیمه ایجاد شده باشند. تنها اقدام منطقی در catch — ثبت لاگ و راهاندازی مجدد Activity است.
محدودیت Heap در دستگاههای مختلف متفاوت است. عملیاتی که به 300 MB نیاز دارد، در دستگاهی با محدودیت 192 MB سقوط میکند، اما در پرچمداری با 512 MB عبور میکند. برای کشف سناریوهای OOM روی دستگاههایی با حداقل مشخصات تست کنید.
largeHeap محدودیت را افزایش میدهد، اما برنامه را سریعتر نمیکند. مکثهای GC طولانیتر میشوند، زیرا جمعآوری Heap بزرگ بیشتر طول میکشد. سیستم ممکن است برنامههای پسزمینه را برای تأمین حافظه از بین ببرد. largeHeap را فقط برای برنامههایی استفاده کنید که واقعاً به حافظه زیادی نیاز دارند (دوربینها، ویرایشگرها).
OOM — استثنایی در داخل برنامه هنگام کمبود Heap است. کشتن سیستمعامل (Low Memory Killer) — تصمیم هسته لینوکس برای کشتن یک فرآیند به منظور آزادسازی حافظه برای برنامههای دیگر است. در کشتن سیستمعامل، برنامه استثنا دریافت نمیکند — فرآیند به سادگی خاتمه مییابد.
فرمول: width × height × bytesPerPixel. ARGB_8888 = 4 بایت/پیکسل، RGB_565 = 2 بایت/پیکسل. Bitmap FullHD (1920 × 1080) در ARGB_8888 = 8.3 MB. Bitmap 4K (3840 × 2160) = 33 MB. همیشه تصاویر را به اندازه لازم برای نمایش روی صفحه مقیاس کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید