Heap Dump: ما هو، تحليل الكومة وإزالة تسريبات الذاكرة

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

Heap Dump (صورة الكومة) هي لقطة للذاكرة الديناميكية للتطبيق تحتوي على معلومات كاملة عن جميع الكائنات الحية: فئاتها وأحجامها ومراجعها المتبادلة وإمكانية الوصول إليها من جذور GC. Heap Dump هو الأداة الرئيسية لتحليل تسريبات الذاكرة وتحسين استهلاك الموارد. وفقاً لـ Android Developers، يتيح تحليل heap dumps اكتشاف ما يصل إلى 95% من تسريبات الذاكرة، بما في ذلك المراجع الدائرية والمستمعين المنسيين والمراجع الثابتة غير المحررة.

الخلاصة

  • Heap Dump هو لقطة للذاكرة الديناميكية الكاملة للتطبيق مع معلومات عن كل كائن والمراجع بينها.
  • Android Studio Memory Profiler يتيح التقاط heap dumps في الوقت الفعلي لتطبيقات Java و Kotlin.
  • Xcode Instruments يوفر أداة Allocations لإنشاء وتحليل heap dumps على iOS/macOS.
  • Shallow و retained size هما المقياسان الرئيسيان: shallow هو حجم الكائن نفسه، retained هو حجم الكائن مضافاً إليه جميع الكائنات التي يحتفظ بها.
  • تحليل heap dump يشمل البحث في dominator tree وأكبر الكائنات retained وأقصر المسارات إلى جذور GC.

ما هو heap dump ولماذا تحتاجه

Heap dump هو تفريغ كامل لكومة الآلة الافتراضية — منطقة الذاكرة حيث توجد جميع الكائنات المنشأة ديناميكياً. في Java و Kotlin هي كومة Dalvik/ART على Android، وفي Swift و Objective-C هي كومة المُدارة بواسطة ARC على iOS. يلتقط heap dump كل كائن وفئته وحجمه وحقوله ومراجعه إلى كائنات أخرى وأعلام إمكانية الوصول من جذور GC (متغيرات المكدس والحقول الثابتة ومراجع JNI).

الهدف الرئيسي من heap dump هو اكتشاف تسريبات الذاكرة. يحدث التسريب عندما يستمر التطبيق في الاحتفاظ بمراجع لكائنات لم تعد هناك حاجة إليها، مما يمنع جمعها بواسطة جامع القمامة (أو تحريرها عبر ARC). الأسباب النموذجية: مستمعي الأحداث غير المسجلين عند إتلاف النشاط؛ المفردات ذات المراجع إلى السياق؛ الإغلاقات التي تلتقط self؛ المجموعات الثابتة التي تُضاف إليها البيانات دون إزالتها. يعطي heap dump صورة دقيقة: أي الكائنات «حية»، وأيها غير ضروري، ومن بالضبط يشير إليها.

وفقاً لـ Google I/O، أكثر من 60% من تقارير الأعطال لتطبيقات Android مرتبطة بـ OutOfMemoryError، وفي 80% من الحالات يكون السبب الجذري تسريب ذاكرة يمكن اكتشافه عبر heap dump. بالنسبة لتطبيقات iOS الوضع مشابه: التسريبات بسبب retain cycles هي أحد الأسباب الأكثر شيوعاً للأعطال، والتي يتم تحديدها عبر أداة Allocations في Xcode.

متى تحتاج heap dump

يجب إجراء heap dump عند ظهور الأعراض التالية: يستهلك التطبيق الذاكرة بشكل خطي أثناء الإجراءات المتكررة (التنقل ذهاباً وإياباً بين الشاشات)؛ بعد إغلاق شاشة، لا تعود الذاكرة إلى مستواها الأصلي؛ تظهر OutOfMemoryError أو تحذيرات الذاكرة على iOS؛ ينهي التطبيق بسبب تجاوز حد الذاكرة (EXC_RESOURCE_RESOURCE على iOS). الجمع المنتظم لـ heap dumps هو جزء من بروتوكول الثقافة الهندسية في مشاريع الجوال الكبيرة مثل Instagram و Spotify.

Heap dump في Android Studio: الالتقاط والتحليل

Android Studio يوفر Memory Profiler — أداة مدمجة لالتقاط heap dumps في الوقت الفعلي. يمكن الوصول إليها عبر View → Tool Windows → Profiler. بعد تشغيل التطبيق، حدد الجلسة وانتقل إلى علامة التبويب Memory وانقر على Dump Java Heap. يوقف Android Studio التطبيق مؤقتاً ويقوم بتفريغ كومة ART ويحمل النتيجة للتحليل. ملف التفريغ بتنسيق .hprof — معيار HPROF المتوافق مع معظم محللات الذاكرة.

بعد تحميل التفريغ، يعرض Android Studio جدول كائنات بأعمدة: Allocations (عدد النسخ)، Native Size (الذاكرة خارج كومة ART)، Shallow Size (ذاكرة الكائن نفسه)، Retained Size (ذاكرة الكائن مع الرسم البياني الفرعي بالكامل). يسمح التصفية حسب اسم الفئة والترتيب حسب retained size والبحث حسب الحزم بالعثور بسرعة على المناطق المشكلة.

kotlin
// تسريب نموذجي — مستمع لم يتم إلغاء تسجيله في onDestroy
class MainActivity : AppCompatActivity() {
    private val sensorManager by lazy {
        getSystemService(SENSOR_SERVICE) as SensorManager
    }
    private val listener = MySensorListener()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sensorManager.registerListener(listener,
            sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
            SensorManager.SENSOR_DELAY_NORMAL)
    }

    override fun onDestroy() {
        super.onDestroy()
        // ❌ مفقود sensorManager.unregisterListener(listener)
        // → لن يتم جمع Activity بواسطة GC، heap dump سيظهر التسريب
    }
}

تحليل dominator tree في Android Studio

علامة التبويب Dominator Tree تظهر الكائنات التي تحتفظ بأكبر قدر من الذاكرة. إذا تمت إزالة كائن من dominator tree، فإن جميع الذاكرة التي يحتفظ بها تصبح متاحة للتجميع. هذه أداة رئيسية: بدلاً من فحص آلاف الكائنات، تركز على 10–20 كائنات تتحكم في 80–90% من الذاكرة. وفقاً لـ Google، تحليل dominator tree هو الطريقة الأكثر فعالية للعثور على نقطة التسريب، مما يقلل وقت التحليل من ساعات إلى دقائق.

Heap dump في Xcode Instruments: Allocations و Leaks

Xcode Instruments يوفر أداتين للعمل مع heap dumps: Allocations — التقاط تفريغ الكومة مع رسم بياني للاستهلاك في الوقت الفعلي؛ Leaks — البحث التلقائي عن التسريبات عبر تحليل retain cycles. يعرض Allocations جميع الكائنات في الكومة وحجمها وعدد الإنشاءات (allocations) والتحريرات (deallocations). الفرق بين عدد الإنشاءات والتحريرات لفئة معينة يشير إلى تسريب محتمل.

يتم التقاط heap dump في Allocations بالزر Snapshot Memory — توقف الأداة التطبيق مؤقتاً وتأخذ تفريغاً كاملاً. بعد ذلك، تتوفر العروض القياسية: قائمة الكائنات حسب الفئة، شجرة الاستدعاءات لكل كائن، ومنشئ التقارير. على عكس Android Studio، لا يستخدم Xcode .hprof، بل يخزن البيانات بتنسيقه الخاص .trace المتوافق مع Instruments.

swift
// تسريب iOS نموذجي — retain cycle عبر إغلاق
class NetworkManager {
    var onComplete: ((Data) -> Void)?

    func startRequest() {
        // ❌ الإغلاق يلتقط self — retain cycle
        onComplete = { data in
            self.process(data)
        }
    }
    func process(_ data: Data) {}
}

أداة Leaks تكتشف تلقائياً retain cycles والتسريبات عبر تحليل رسم بياني للمراجع. تضع علامة على الكائنات المسربة بأيقونة أرجوانية وتظهر المسار إلى الجذر (GC root). لإزالة retain cycle، يكفي إضافة [weak self] أو [unowned self] في التقاط الإغلاق. التشغيل المنتظم لأداة Leaks هو خطوة إلزامية في pipeline CI في الفرق التي تستخدم Swift لتطوير iOS.

swift
// الإصلاح — مرجع ضعيف إلى self
onComplete = { [weak self] data in
    guard let self else { return }
    self.process(data)
}

Shallow size و retained size و dominator tree

للتحليل الصحيح لـ heap dump، من الضروري فهم ثلاثة مقاييس رئيسية. Shallow size هو حجم الذاكرة التي يشغلها الكائن مباشرة: حقوله ورأسه (header) والمحاذاة. لكائن Java/Kotlin نموذجي، يتراوح shallow size بين 16–40 بايت. Retained size هو shallow size للكائن مضافاً إليه مجموع shallow size لجميع الكائنات التي يمكن الوصول إليها فقط عبر هذا الكائن (أي ستصبح قمامة عند إزالته). تحديداً retained size يظهر التأثير الحقيقي للكائن على استهلاك الذاكرة.

المقياسالوصفمثال
Shallow sizeحجم الكائن نفسه بالبايتBitmap (100×100) = 40 016 B
Retained sizeShallow size + كل ما يحتفظ بهActivity مع View Tree = 2–5 MB
Deep sizeRetained size + كائنات متداخلة من رسوم بيانية أخرىScrollView مع محول = 10–50 MB

Dominator tree هو هيكل حيث يشير كل كائن إلى «المسيطر» الخاص به — الكائن الذي يتحكم في إمكانية الوصول إليه. إذا تمت إزالة المسيطر، تصبح جميع كائنات الشجرة الفرعية الخاصة به قمامة. تحليل dominator tree هو أسرع طريقة لمعرفة أي كائن يحتفظ بأكبر قدر من الذاكرة. وفقاً لـ Eclipse MAT (Memory Analyzer Tool)، يتم اكتشاف 90% من التسريبات عبر مراجعة top-20 من dominator tree في 5 دقائق.

تحليل تسريبات الذاكرة عبر heap dump

تتكون عملية تحليل التسريب عبر heap dump من عدة خطوات. الخطوة 1: قم بالإجراء الذي يجب أن يحرر الذاكرة (أغلق الشاشة، أنهِ العملية). الخطوة 2: استدعِ GC (System.gc() في Android، لقطة إجبارية في Xcode) وقم بعمل heap dump. الخطوة 3: ابحث عن الكائنات التي كان يجب تدميرها (على سبيل المثال، نسخة Activity بعد finish). الخطوة 4: للكائن المشبوه، قم بتشغيل Path to GC Roots — سلسلة المراجع التي تبقي الكائن حياً. آخر مرجع في السلسلة هو سبب التسريب.

Path to GC Roots

وظيفة Path to GC Roots متاحة في Android Studio Profiler و Eclipse MAT و Xcode Instruments. تظهر أقصر سلسلة مراجع من جذر GC إلى الكائن المشكل. باستبعاد المراجع الضعيفة (weak) والناعمة (soft)، تحصل فقط على القوية (strong) — تلك التي تمنع التجميع بالفعل. وفقاً لـ Square Engineering، 70% من التسريبات في تطبيقات Android ناتجة عن نمطين فقط: مراجع ثابتة إلى Activity أو Context ومستمعين مسجلين ولكن غير مسجلين الإلغاء.

kotlin
// مثال على تسريب عبر مرجع ثابت
object AppCache {
    private val cache = mutableMapOf<String, Any>()

    fun storeActivityReference(activity: Activity) {
        cache["current_activity"] = activity // ❌ تسريب!
    }
}

// الإصلاح: مرجع ضعيف
object AppCacheFixed {
    private val cache = mutableMapOf<String, WeakReference<Any>>()
}

مقارنة اثنين من heap dumps

تقنية وضع المقارنة هي واحدة من أكثر الطرق فعالية لاكتشاف التسريبات. قم بعمل heap dump قبل وبعد إجراء متكرر (على سبيل المثال، خمس انتقالات إلى شاشة والعودة). قارن عدد نسخ الفئات الرئيسية: إذا زاد عدد Activity على الرغم من إغلاق جميع الأنشطة — فهذا تسريب. يدعم Android Studio و Eclipse MAT المقارنة التلقائية للتفريغ مع إبراز الاختلافات. وفقاً لـ Google، تسمح مقارنة التفريغ باكتشاف التسريبات غير المرئية في التحليل الفردي بفضل تراكم التأثير.

توصيات عملية لتقليل استهلاك الذاكرة

بناءً على تحليل heap dump في مشاريع حقيقية، تم تطوير ممارسات مثبتة لتحسين الذاكرة. استخدم WeakReference للذاكر المؤقتة والاستدعاءات الخلفية والمراجع إلى السياق في الكائنات طويلة العمر. ألغِ تسجيل المستمعين في onPause/onDestroy لنظام Android و deinit لنظام iOS. تجنب المجموعات الثابتة الكبيرة — إذا كانت ضرورية، استخدم LruCache مع حد للحجم. حسّن الـ Bitmaps: حمّل الصور بـ inSampleSize الصحيح، واستخدم Glide أو Picasso مع ذاكرة تخزين مؤقت على القرص.

توصيف الذاكرة أثناء التطوير

أدرج الالتقاط المنتظم لـ heap dump في pipeline CI الخاص بك. قم بإعداد مهمة تشغل اختبارات واجهة مستخدم مقيسة وتنفذ سيناريوهات المستخدم الرئيسية وتقارن heap dump بخط الأساس. إذا زاد retained size بأكثر من 5% عن خط الأساس، يتم وضع علامة على البناء كتراجع. يُمارس هذا النهج في Airbnb و Uber وشركات أخرى ذات متطلبات جودة عالية. وفقاً لـ Uber Engineering، أدى تنفيذ التحليل التلقائي لـ heap dump في CI إلى تقليل الأخطاء المرتبطة بالذاكرة بنسبة 70% في ربع سنة.

groovy
// مثال مهمة Gradle لـ heap dump تلقائي في CI
task profileMemory(type: Exec) {
    commandLine 'adb', 'shell',
        'am start -n com.example/.MainActivity'
    // انتظار التحميل
    doLast {
        exec { commandLine 'adb', 'shell',
            'am broadcast -a com.example.DUMP_HEAP' }
    }
}

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

ما الفرق بين shallow size و retained size؟

Shallow size هو حجم الكائن نفسه (الحقول + الرأس). Retained size هو حجم الكائن مضافاً إليه جميع الكائنات التي ستصبح قمامة عند إزالته. Retained size هو المؤشر الرئيسي لتأثير الكائن على استهلاك الذاكرة.

كيفية عمل heap dump على جهاز Android فعلي؟

عبر Android Studio Profiler، حدد الجهاز والعملية، وانقر على Dump Java Heap. بديلاً — عبر سطر الأوامر: adb shell am dumpheap PID /sdcard/dump.hprof، ثم adb pull.

لماذا يمكن أن يكون heap dump ضخماً (500 MB+)؟

يتضمن heap dump جميع الكائنات الحية. إذا كان التطبيق يستخدم ذاكرات مؤقتة أو Bitmaps أو يعالج بيانات كبيرة، يمكن أن يصل التفريغ إلى مئات الميغابايت. قم بالتصفية حسب الفئات أو استخدم Eclipse MAT لتحميل الفهرس فقط.

هل يمكن تحليل heap dump بدون Android Studio؟

نعم، استخدم Eclipse MAT (Memory Analyzer Tool) — أداة مجانية لتحليل ملفات .hprof. تدعم dominator tree و path to GC roots ومقارنة التفريغ والكشف التلقائي عن التسريبات عبر Leak Suspects Report.

هل يقلل heap dump من أداء التطبيق؟

التفريغ نفسه — نعم، لأن جمع التفريغ يوقف جميع سلاسل التنفيذ (stop-the-world). بدون تفريغ — لا. قم بالتفريغ في بيئات خاضعة للرقابة (منصة اختبار، CI)، وليس في الإنتاج.

الملخص

  • Heap Dump هو لقطة كاملة لكومة التطبيق مع معلومات عن كل كائن والعلاقات بينها.
  • Android Studio Memory Profiler و Xcode Instruments Allocations هما أدوات الالتقاط الرئيسية.
  • Shallow size هو حجم الكائن نفسه؛ retained size هو حجم الكائن مع الرسم البياني الفرعي للتبعيات بالكامل.
  • Dominator tree يظهر الكائنات التي تتحكم في أكبر قدر من الذاكرة.
  • Path to GC Roots هو سلسلة المراجع القوية التي تمنع الكائن من جمع القمامة.
  • مقارنة اثنين من heap dumps (قبل/بعد الإجراء) هي الطريقة الأكثر موثوقية لاكتشاف التسريبات.
  • أتمتة التقاط وتحليل heap dumps في CI تمنع تراجع الذاكرة أثناء التطوير.

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

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

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

اقرأ أيضًا