OutOfMemoryError هو استثناء fatal يحدث عندما لا تستطيع الآلة الافتراضية لجافا (JVM) أو Android Runtime (ART) تخصيص ذاكرة لكائن جديد بسبب عدم وجود مساحة كافية في Heap. وفقًا لـ Square Engineering، 70% من أخطاء OutOfMemoryError في التطبيقات المحمولة ناتجة عن تسرب الذاكرة، وليس عن تجاوز الحد الفعلي. فهم أسباب OOM هو مفتاح استقرار التطبيق.
الخلاصة
OutOfMemoryError (OOM) هو استثناء من عائلة VirtualMachineError في Java/Kotlin يشير إلى عدم القدرة على تخصيص ذاكرة لكائن جديد. على عكس الاستثناءات المفحوصة، OOM هو Error ولا يتطلب معالجة عبر catch — على الرغم من أنه يمكن التقاطه تقنيًا. بعد حدوث OOM، يكون التطبيق عادة في حالة غير مستقرة ويوصى بإنهائه.
على Android، كل تطبيق له حد Heap محدد من قبل الشركة المصنعة للجهاز. للهواتف الذكية الحديثة التي بها 6+ جيجابايت RAM، الحد هو 256–512 ميجابايت، للأجهزة الاقتصادية — 128–192 ميجابايت. عندما يتجاوز الحجم الإجمالي لجميع الكائنات الحية هذا الحد، يطرح ART استثناء OutOfMemoryError.
من المهم فهم: OOM لا يعني دائمًا أن الجهاز نفدت منه الذاكرة الفعلية. يعني أن التطبيق استنفد حد Heap الخاص به الذي حدده النظام. التطبيقات الأخرى قد يكون لديها ذاكرة خالية، لكن تطبيقك لا يمكنه استخدامها بسبب عزل العمليات في Android.
خمسة سيناريوهات تؤدي بانتظام إلى OOM في التطبيقات المحمولة. كل سيناريو مرتبط بنوع بيانات أو عملية محددة.
Bitmap هو المستهلك الرئيسي للذاكرة في تطبيقات Android. تحميل صورة FullHD (1920 × 1080) بالحجم الأصلي يستهلك 8.3 ميجابايت بصيغة ARGB_8888. إذا كان هناك 50 صورة من هذا القبيل في RecyclerView، فهذا 415 ميجابايت، متجاوزًا Heap أي جهاز. تحميل الصور بدون inSampleSize يضمن OOM على الأجهزة الضعيفة.
استخدم Glide أو Coil للتحجيم التلقائي. هذه المكتبات تحمّل الصور بحجم يتوافق مع View وليس مع الدقة الأصلية. للاستخدام المباشر لـ BitmapFactory.Options، طبق inSampleSize: احسبه كقوة للعدد 2 بحيث لا يتجاوز الحجم النهائي 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 في المشروع للكشف التلقائي عن التسريبات. سيظهر كل كائن مسرب مع تتبع دقيق للمكدس. بعد إصلاح جميع التسريبات، يصبح استهلاك 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-loop لا تسع في Heap قبل أن يتمكن GC من جمعها. هذا أكثر شيوعًا في الألعاب ومحررات الرسوم.
استخدم Object Pool للكائنات التي يتم إنشاؤها وتدميرها بشكل جماعي. للبيانات الرقمية، استخدم الأنواع البدائية (FloatArray بدلاً من List<Float>). RecyclerView مع ViewHolder Pool يحل هذه المشكلة لمكونات واجهة المستخدم.
التجزئة هي حالة حيث توجد ذاكرة خالية كافية إجمالاً، لكن لا يوجد كتلة متجاورة لكائن جديد. ART يقوم بضغط Heap أثناء GC، لكن ليس دائمًا بنجاح. المصفوفات الكبيرة (Bitmap, byte[]) هي الأكثر حساسية للتجزئة.
ART على Android 8+ يستخدم GC الأجيال، الذي يقلل التجزئة عن طريق فصل الكائنات الشابة والقديمة. مع ذلك، تجنب تخصيص أجزاء بأحجام مختلفة في نفس المجموعة — حاول استخدام مخازن مؤقتة مسبقة التخصيص ذات حجم ثابت.
حد Heap في Android ليس ثابتًا — يعتمد على الشركة المصنعة وطراز الجهاز وإصدار نظام التشغيل. تحدد Google الحد الأدنى من المتطلبات من خلال وثيقة تعريف التوافق (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 قابلًا للتكرار، قلل Heap عبر android:smallHeap في بناء التصحيح أو استخدم DDMS مع استدعاء GC يدوي.
الخطوة 2: خذ Heap Dump في ذروة التحميل (قبل OOM). افتح Dump في Android Studio: علامة التبويب Classes مرتبة حسب Retained Size. أكبر الكائنات هي Bitmap, byte[], String. لكل Bitmap، تحقق من الحجم (العرض × الارتفاع × 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 يفصل البيانات عن واجهة المستخدم ويمنع الاحتفاظ بـ View عند تدوير الشاشة. ViewModel يعمر أكثر من Activity، بياناته لا تُفقد، ويمكن إعادة إنشاء View دون تكرار البيانات في الذاكرة. استخدم StateFlow بدلاً من LiveData للإدارة الصريحة للحالات.
Glide هي مكتبة إلزامية للعمل مع الصور. تقوم تلقائيًا بالتحجيم والتخزين المؤقت (قرص + ذاكرة) وإعادة تدوير Bitmap. قم بتكوين diskCacheStrategy و skipMemoryCache للقوائم الكبيرة. للصور المتحركة، استخدم Glide مع GIF/WebP — تستهلك ذاكرة أقل من سلسلة من Bitmaps.
Firebase Performance Monitoring يتتبع استهلاك الذاكرة في الوقت الفعلي. قم بتعيين تنبيه على استخدام Heap يتجاوز 80% من الحد — هذه إشارة للتحقق. Crashlytics يجمع OOM كاستثناء ويظهر آخر حالة معروفة لـ Heap قبل التعطل. لـ Android 11+، استخدم ApplicationExitInfo للكشف عن إنهاءات OOM.
تأكد من اختبار التطبيق على الأجهزة ذات 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 بايت/بكسل. Bitmap FullHD (1920 × 1080) بصيغة ARGB_8888 = 8.3 ميجابايت. Bitmap بدقة 4K (3840 × 2160) = 33 ميجابايت. قم دائمًا بتحجيم الصور إلى الحجم المطلوب للعرض على الشاشة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا