تسرب الذاكرة والانتفاخ — ما هو، الأسباب وكيفية التجنب

المؤلف: IT Sectr نُشر: 2026-07-28 وقت القراءة: 10 دق

تسرب الذاكرة — واحدة من أكثر المشاكل غدراً في تطوير التطبيقات المحمولة. استخدام التطبيق للذاكرة ينمو باستمرار حتى يصل إلى الحد الذي يحدده نظام التشغيل، يتبعه OutOfMemoryError أو إنهاء إجباري. وفقاً لـ Square Engineering، حوالي 40% من تطبيقات Android لديها على الأقل تسرب ذاكرة واحد لا يمكن اكتشافه إلا من خلال التنميط. دعنا نستعرض الأسباب وطرق منع نمو الذاكرة.

الخلاصة

  • قابلية الوصول GC — لا يتم حذف الكائن إذا كان هناك مرجع نشط من المجموعة الجذرية
  • المراجع الثابتة إلى Activity أو Context — السبب الأكثر شيوعاً للتسرب في Android
  • LeakCanary — الأداة القياسية للكشف التلقائي عن التسربات في Android
  • WeakReference — حل للمراجع التي لا يجب أن تمنع جمع القمامة
  • مكونات دورة الحياة تلغي الاشتراكات تلقائياً عند تدمير العرض

ما هو تسرب الذاكرة وانتفاخ التطبيق؟

تسرب الذاكرة — حالة يستمر فيها الاحتفاظ بكائن لم يعد التطبيق بحاجته في heap لأن مرجعاً نشطاً من المجموعة الجذرية (GC Root) لا يزال يشير إليه. يعتبر جامع القمامة مثل هذا الكائن حياً ولا يزيله.

انتفاخ الذاكرة — مشكلة أوسع حيث يستهلك التطبيق ذاكرة أكثر من اللازم لأداء مهامه الحالية. الأسباب: التخزين المؤقت المفرط، تكرار الكائنات، هياكل البيانات غير المثلى، وتجزئة heap.

في Android، يتم تخصيص heap محدود لكل تطبيق (عادة 64–512 ميغابايت حسب الجهاز وإصدار نظام التشغيل). في iOS، الحد أقل صرامة، لكن النظام يرسل تحذيراً عند الاقتراب من الحد.

الخاصيةAndroidiOS
حد heap64–512 ميغابايت (حسب الجهاز)ضمني (نظام)
جمع القمامةART (متزامن، مضغوط)ARC (العد التلقائي للمراجع)
آلية التسربمراجع GC Rootدورات الاحتفاظ (دورات مرجعية قوية)
النتيجةOutOfMemoryErrorتحذير ذاكرة → إنهاء

وفقاً لـ Facebook Engineering Blog، تتسبب تسربات الذاكرة في ~15% من تقارير الأعطال في التطبيقات المحمولة. في Android، يضاف إلى ذلك ANR بسبب توقفات GC المتكررة عند انخفاض الذاكرة.

أنماط تسرب الذاكرة الشائعة في Android و iOS

مرجع ثابت إلى Activity — تسرب كلاسيكي في Android. إذا كان حقل ثابت أو singleton يحتفظ بمرجع إلى Activity، فلن يتم جمعها بواسطة GC حتى بعد finish() ما دام singleton حياً. Activity هي كائن ثقيل يحتوي على تسلسل هرمي للعرض وموارد و Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // leak: static reference to Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
    }
}

الفئات المجهولة واللامبدات — تحتفظ ضمنياً بمرجع إلى الفئة الخارجية. إذا تم تمرير Runnable أو Callback إلى خدمة خارجية وتم تدمير Activity، فإن كائن الفئة المجهولة لا يزال في قائمة الانتظار ويمنع Activity من جمع القمامة.

  • Handler مع تأخير — إذا تم تدمير Activity ولكن Handler.postDelayed لم ينفذ بعد، فإن Activity تتسرب
  • Thread و AsyncTask — عند تدوير الشاشة، يتم إعادة إنشاء Activity بينما يستمر Thread القديم في الاحتفاظ بمرجع إلى Activity القديمة
  • Retrofit/Callback — Callback مجهول يحتفظ بمرجع إلى presenter أو fragment
  • المراقبون — اشتراكات LiveData أو RxJava دون إلغاء عند onDestroy

في iOS، المشكلة الرئيسية هي دورات الاحتفاظ: كائنان يحتفظان بمراجع قوية لبعضهما البعض، ولا يستطيع ARC تصفير عداد المراجع لأي منهما. حالة نموذجية: closure يلتقط self بقوة، و self يحتفظ بمرجع إلى closure.

كيفية اكتشاف تسربات الذاكرة؟

LeakCanary — مكتبة من Square للكشف التلقائي عن التسربات في Android. بعد تدمير Activity أو Fragment، تتحقق مما إذا كان الكائن قد تم جمعه بواسطة GC. إذا لم يكن كذلك، تقوم بعمل heap dump وتظهر تتبع التسرب.

kotlin
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary auto-installs in debug build
        // via ContentProvider — zero code setup
    }
}

// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — أداة مدمجة لمراقبة الذاكرة في الوقت الفعلي. تسمح بتسجيل heap dump، والعثور على كائنات مشبوهة (Retained Size > 1 ميغابايت)، وتتبع مسار GC root إلى كل كائن.

لـ iOS، استخدم Xcode Memory Graph Debugger. يقوم بتصور رسم بياني للكائنات في الذاكرة، ويظهر دورات الاحتفاظ ويسمح باكتشاف المراجع الدائرية فوراً. Instruments > Allocations متاح أيضاً للمراقبة طويلة المدى.

استراتيجيات الوقاية

WeakReference — آلية أساسية للمراجع التي لا يجب أن تتداخل مع جمع القمامة. إذا قرر GC جمع كائن، فإن WeakReference يعيد null. يُستخدم للمعاودات والمستمعين والمراجع لمكونات واجهة المستخدم من خيوط الخلفية.

مكونات دورة الحياة — نهج معماري مطبق في Android Jetpack (Lifecycle، LiveData، Flow، coroutines). يتم إلغاء الاشتراكات تلقائياً عند onDestroy، مما يلغي الفئة الرئيسية من التسربات.

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // coroutine auto-cancels on onCleared()
        }
    }
}

viewModelScope و lifecycleScope — CoroutineScope مدمجان في Android يتم إلغاؤهما عند حدث دورة الحياة المقابل. هذا يلغي التسربات عبر coroutines — السيناريو الأكثر شيوعاً في تطوير Android الحديث.

  • لا تستخدم مراجع ثابتة إلى Context أو Activity أو View أو Fragment
  • ألغِ جميع اشتراكات RxJava في disposeBag / CompositeDisposable عند onDestroy
  • استخدم [weak self] / [unowned self] في closures iOS لمنع دورات الاحتفاظ
  • تحقق من Bitmap والكائنات الكبيرة — يجب إعادة تدويرها أو إلغاؤها

أدوات تنميط الذاكرة

Memory Profiler في Android Studio — الأداة الأساسية لمراقبة heap. يعرض التخصيصات المباشرة ولقطات heap وعدد الكائنات حسب النوع. يسمح بتسجيل تفريغ وتحليله في MAT (Memory Analyzer Tool) للعثور على كائنات مشبوهة.

Eclipse MAT — محلل سطح مكتب لـ heap dump. بعد تحميل ملف HPROF من Android Studio، يبني MAT شجرة مسيطرة، ويظهر retain size لكل كائن، ويقدم تحليلاً تلقائياً للتسربات المشبوهة عبر Leak Suspects Report.

Xcode Memory Graph — مصحح بصري لدورات الاحتفاظ. عند النقر على زر Memory Graph Debugger، يوقف Xcode التطبيق، ويبني رسماً بيانياً كاملاً للكائنات في الذاكرة، ويبرز دورات الاحتفاظ باللون الأحمر.

الأداةالمنصةالميزة
LeakCanaryAndroidكشف تلقائي للتسربات بعد destroy
Memory ProfilerAndroid StudioHeap dump + تخصيصات مباشرة
Eclipse MATAndroidشجرة مسيطرة، Leak Suspects Report
Memory GraphiOS (Xcode)مرئي دورات الاحتفاظ

وفقاً لـ Google I/O 2023، التطبيقات التي تستخدم LeakCanary في إصدارات التصحيح تقلل الأعطال المتعلقة بالذاكرة بنسبة 30–50% في أول شهرين بعد التبني. يُوصى بإضافة LeakCanary خلال مرحلة بدء المشروع.

الأسئلة الشائعة

ما الفرق بين تسرب الذاكرة والانتفاخ؟

التسرب — كائنات لا يمكن للكود الوصول إليها ولكن لا يتم جمعها بواسطة GC بسبب مراجع نشطة. الانتفاخ — التطبيق يحتفظ بكائنات مطلوبة منطقياً ولكن بكمية زائدة (مثلاً، ذاكرة تخزين مؤقت 50 ميغابايت في تطبيق يعمل بحجم 80 ميغابايت). الانتفاخ يُعالج معملياً، التسرب — من خلال إدارة صحيحة للمراجع.

كيف يجد LeakCanary التسربات؟

LeakCanary يستخدم ObjectWatcher — بعد onDestroy() لـ Activity، ينشئ WeakReference إلى Activity ويشغل GC. إذا لم يتم مسح WeakReference بعد 5 ثوانٍ، يقوم LeakCanary بعمل heap dump، ويحلل أقصر سلسلة مرجعية من GC Root إلى الكائن، ويظهر مكدس التسرب الدقيق مع اسم الملف ورقم السطر.

لماذا يسبب Bitmap غالباً OutOfMemoryError؟

Bitmap يشغل ذاكرة خارج heap Java في الذاكرة الأصلية (native heap). حجم Bitmap واحد = العرض × الارتفاع × 4 بايت (ARGB_8888). صورة 12 ميغابكسل (4000×3000) تشغل 48 ميغابايت. لا يستطيع Android دائماً تحرير الذاكرة الأصلية في الوقت المناسب، لذا تراكم عدة Bitmaps يؤدي إلى OOM حتى مع وجود heap Java كافٍ.

ما هي دورة الاحتفاظ في iOS؟

دورة الاحتفاظ — حالة في ARC حيث يحتفظ كائنان بمراجع قوية لبعضهما البعض، ولا يصل عداد المراجع أبداً إلى الصفر. مثال نموذجي: ViewController بمرجع قوي إلى closure، ويلتقط closure self بقوة. الحل: استخدم [weak self] أو [unowned self] في closures.

ما هو الحد الأقصى لحجم heap على Android؟

حجم heap يعتمد على الجهاز وإصدار Android. للأجهزة القديمة (API 15–24) — 64–128 ميغابايت. للأجهزة الحديثة (API 25+) — 256–512 ميغابايت. يمكن الحصول على القيمة الدقيقة عبر ActivityManager.getMemoryClass(). للتطبيقات الكبيرة (الألعاب، المحررات)، largeHeap=true في البيان يوفر حتى 1 غيغابايت.

الملخص

  • تسرب الذاكرة — كائن لا يتم جمعه بواسطة GC بسبب مرجع نشط من المجموعة الجذرية؛ الانتفاخ — استهلاك مفرط للذاكرة دون تسربات واضحة
  • المراجع الثابتة إلى Activity أو Context أو View — السبب الأول للتسربات في Android؛ الحل — WeakReference أو Application Context
  • الفئات المجهولة واللامبدات تحتفظ ضمنياً بمرجع إلى الفئة الخارجية؛ المعاودات غير الملغاة هي ثاني أكثر سبب شيوعاً
  • LeakCanary — المعيار للكشف التلقائي عن التسربات في Android؛ التكامل يستغرق 5 دقائق ويقلل معدل الأعطال بنسبة 30–50%
  • lifecycleScope و viewModelScope يلغيان coroutines تلقائياً عند التدمير، مما يلغي فئة كاملة من التسربات
  • دورات الاحتفاظ في iOS تُحل باستخدام weak/unowned self في closures والمفوضين
  • نمطط الذاكرة مرة واحدة على الأقل لكل سباق — يجب أن يصبح heap dump باستخدام MAT أو Memory Graph جزءاً من مراجعة الكود

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا