Heap Dump (صورة الكومة) هي لقطة للذاكرة الديناميكية للتطبيق تحتوي على معلومات كاملة عن جميع الكائنات الحية: فئاتها وأحجامها ومراجعها المتبادلة وإمكانية الوصول إليها من جذور GC. Heap Dump هو الأداة الرئيسية لتحليل تسريبات الذاكرة وتحسين استهلاك الموارد. وفقاً لـ Android Developers، يتيح تحليل heap dumps اكتشاف ما يصل إلى 95% من تسريبات الذاكرة، بما في ذلك المراجع الدائرية والمستمعين المنسيين والمراجع الثابتة غير المحررة.
الخلاصة
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 عند ظهور الأعراض التالية: يستهلك التطبيق الذاكرة بشكل خطي أثناء الإجراءات المتكررة (التنقل ذهاباً وإياباً بين الشاشات)؛ بعد إغلاق شاشة، لا تعود الذاكرة إلى مستواها الأصلي؛ تظهر OutOfMemoryError أو تحذيرات الذاكرة على iOS؛ ينهي التطبيق بسبب تجاوز حد الذاكرة (EXC_RESOURCE_RESOURCE على iOS). الجمع المنتظم لـ heap dumps هو جزء من بروتوكول الثقافة الهندسية في مشاريع الجوال الكبيرة مثل Instagram و Spotify.
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 والبحث حسب الحزم بالعثور بسرعة على المناطق المشكلة.
// تسريب نموذجي — مستمع لم يتم إلغاء تسجيله في 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 تظهر الكائنات التي تحتفظ بأكبر قدر من الذاكرة. إذا تمت إزالة كائن من dominator tree، فإن جميع الذاكرة التي يحتفظ بها تصبح متاحة للتجميع. هذه أداة رئيسية: بدلاً من فحص آلاف الكائنات، تركز على 10–20 كائنات تتحكم في 80–90% من الذاكرة. وفقاً لـ Google، تحليل dominator tree هو الطريقة الأكثر فعالية للعثور على نقطة التسريب، مما يقلل وقت التحليل من ساعات إلى دقائق.
Xcode Instruments يوفر أداتين للعمل مع heap dumps: Allocations — التقاط تفريغ الكومة مع رسم بياني للاستهلاك في الوقت الفعلي؛ Leaks — البحث التلقائي عن التسريبات عبر تحليل retain cycles. يعرض Allocations جميع الكائنات في الكومة وحجمها وعدد الإنشاءات (allocations) والتحريرات (deallocations). الفرق بين عدد الإنشاءات والتحريرات لفئة معينة يشير إلى تسريب محتمل.
يتم التقاط heap dump في Allocations بالزر Snapshot Memory — توقف الأداة التطبيق مؤقتاً وتأخذ تفريغاً كاملاً. بعد ذلك، تتوفر العروض القياسية: قائمة الكائنات حسب الفئة، شجرة الاستدعاءات لكل كائن، ومنشئ التقارير. على عكس Android Studio، لا يستخدم Xcode .hprof، بل يخزن البيانات بتنسيقه الخاص .trace المتوافق مع Instruments.
// تسريب 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.
// الإصلاح — مرجع ضعيف إلى self
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
للتحليل الصحيح لـ 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 size | Shallow size + كل ما يحتفظ به | Activity مع View Tree = 2–5 MB |
| Deep size | Retained size + كائنات متداخلة من رسوم بيانية أخرى | ScrollView مع محول = 10–50 MB |
Dominator tree هو هيكل حيث يشير كل كائن إلى «المسيطر» الخاص به — الكائن الذي يتحكم في إمكانية الوصول إليه. إذا تمت إزالة المسيطر، تصبح جميع كائنات الشجرة الفرعية الخاصة به قمامة. تحليل dominator tree هو أسرع طريقة لمعرفة أي كائن يحتفظ بأكبر قدر من الذاكرة. وفقاً لـ Eclipse MAT (Memory Analyzer Tool)، يتم اكتشاف 90% من التسريبات عبر مراجعة top-20 من dominator tree في 5 دقائق.
تتكون عملية تحليل التسريب عبر heap dump من عدة خطوات. الخطوة 1: قم بالإجراء الذي يجب أن يحرر الذاكرة (أغلق الشاشة، أنهِ العملية). الخطوة 2: استدعِ GC (System.gc() في Android، لقطة إجبارية في Xcode) وقم بعمل heap dump. الخطوة 3: ابحث عن الكائنات التي كان يجب تدميرها (على سبيل المثال، نسخة Activity بعد finish). الخطوة 4: للكائن المشبوه، قم بتشغيل 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 ومستمعين مسجلين ولكن غير مسجلين الإلغاء.
// مثال على تسريب عبر مرجع ثابت
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 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% في ربع سنة.
// مثال مهمة 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 هو حجم الكائن مضافاً إليه جميع الكائنات التي ستصبح قمامة عند إزالته. Retained size هو المؤشر الرئيسي لتأثير الكائن على استهلاك الذاكرة.
عبر Android Studio Profiler، حدد الجهاز والعملية، وانقر على Dump Java Heap. بديلاً — عبر سطر الأوامر: adb shell am dumpheap PID /sdcard/dump.hprof، ثم adb pull.
يتضمن heap dump جميع الكائنات الحية. إذا كان التطبيق يستخدم ذاكرات مؤقتة أو Bitmaps أو يعالج بيانات كبيرة، يمكن أن يصل التفريغ إلى مئات الميغابايت. قم بالتصفية حسب الفئات أو استخدم Eclipse MAT لتحميل الفهرس فقط.
نعم، استخدم Eclipse MAT (Memory Analyzer Tool) — أداة مجانية لتحليل ملفات .hprof. تدعم dominator tree و path to GC roots ومقارنة التفريغ والكشف التلقائي عن التسريبات عبر Leak Suspects Report.
التفريغ نفسه — نعم، لأن جمع التفريغ يوقف جميع سلاسل التنفيذ (stop-the-world). بدون تفريغ — لا. قم بالتفريغ في بيئات خاضعة للرقابة (منصة اختبار، CI)، وليس في الإنتاج.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا