نظرة عامة على إدارة الذاكرة

يستخدم كل من "وقت تشغيل Android" (ART) وآلة Dalvik الافتراضي نقل البيانات وربط الذاكرة (mmapping) لإدارة الذاكرة. وهذا يعني أنّ أي ذاكرة يعدّلها تطبيق ما تظل متوفّرة في ذاكرة الوصول العشوائي (RAM) ولا يمكن نقلها إلى صفحات، سواء من خلال تخصيص عناصر جديدة أو لمس صفحات تم ربطها. والطريقة الوحيدة لتحرير الذاكرة من تطبيق ما هي إطلاق مَراجع الكائنات التي يحتفظ بها التطبيق، ما يجعل الذاكرة متاحة لأداة تجميع البيانات غير المرغوب فيها. ويشمل ذلك استثناءً واحدًا: أي ملفات يتم إدخالها بدون تعديل، مثل الرمز البرمجي، يمكن أن يتم نقلها خارج ذاكرة الوصول العشوائي (RAM) إذا أراد النظام استخدام تلك الذاكرة في مكان آخر.

توضّح هذه الصفحة الآلية التي يتّبعها نظام Android لإدارة عمليات التطبيق وتخصيص الذاكرة. لمزيد من المعلومات حول كيفية إدارة الذاكرة بكفاءة أكبر في تطبيقك، يمكنك الاطّلاع على المقالة إدارة الذاكرة في تطبيقك.

جمع القمامة

تعمل بيئة الذاكرة المُدارة، مثل جهاز ART أو Dalvik الافتراضي، على تتبُّع كل عملية تخصيص للذاكرة. وبعد أن يتبيّن أنّ البرنامج لم يعُد مستخدمًا لجزء من الذاكرة، يتم تفريغه إلى كومة الذاكرة المؤقتة بدون أي تدخّل من المبرمج. تُعرف آلية استرداد الذاكرة غير المستخدمة في بيئة الذاكرة المُدارة باسم جمع البيانات غير المرغوب فيها. يهدف جمع البيانات المهملة إلى هدفَين: العثور على عناصر البيانات في برنامج لا يمكن الوصول إليها في المستقبل، واستعادة الموارد التي تستخدمها هذه العناصر.

يتم تخصيص كومة الذاكرة في نظام التشغيل Android من خلال توليف، وهذا يعني أنّ هناك مجموعات بيانات مختلفة من عمليات التخصيص التي يتم تتبّعها، وذلك بناءً على العمر المتوقع للكائن الذي يتم تخصيصه وحجمه. على سبيل المثال، تنتمي العناصر التي تم تخصيصها مؤخرًا إلى جيل الشباب. عندما يظلّ الكائن نشطًا لفترة كافية، يمكن أن تتم ترقيته إلى الجيل الأقدم، يليه بعد ذلك الجيل الدائم.

لكل جيل كومة الذاكرة حدود أعلى مخصصة له بشأن حجم الذاكرة الذي يمكن أن تشغله العناصر. وفي أي وقت يبدأ فيه جيلٌ ما بالامتلاء، ينفِّذ النظام حدث جمع البيانات غير المرغوب فيها في محاولة لإخلاء بعض الذاكرة. وتعتمد مدة جمع البيانات المهملة على جيل الكائنات التي يتم جمعها وعدد العناصر النشطة في كل جيل.

على الرغم من أنّ عملية جمع البيانات المهملة يمكن أن تتم بسرعة كبيرة، لا يزال من الممكن التأثير في أداء تطبيقك. وأنت لا تتحكّم بوجه عام في وقت وقوع حدث جمع البيانات غير المرغوب فيها من داخل رمزك. يتضمن النظام مجموعة قيد التشغيل من المعايير لتحديد موعد تنفيذ عملية جمع البيانات غير الضرورية. عند استيفاء المعايير، يتوقف النظام عن تنفيذ العملية ويبدأ في جمع البيانات غير الضرورية. وقد يزيد وقت المعالجة إذا حدث تجميع البيانات المهملة في منتصف حلقة معالجة مكثّفة، مثل صورة متحركة أو أثناء تشغيل الموسيقى. ومن المحتمل أن تؤدي هذه الزيادة إلى دفع عملية تنفيذ الرمز البرمجي في تطبيقك إلى ما يتجاوز الحدّ الأدنى المقترَح وهو 16 ملي ثانية لعرض الإطارات بفعالية وسلاسة.

بالإضافة إلى ذلك، قد يؤدي مسار الرمز تنفيذ أنواع من الأعمال التي تفرض الحدوث بمعدل تكرار أكبر لأحداث جمع البيانات غير المرغوب فيها أو تجعلها تدوم لفترة أطول من المعتاد. على سبيل المثال، إذا خصصت عناصر متعددة في الجزء الداخلي من حلقة التكرار مقابل كل إطار من رسم متحرك يمزج ألفا، قد يؤدي ذلك إلى إتلاف كومة الذاكرة المؤقتة باستخدام الكثير من العناصر. وفي هذه الحالة، ينفّذ جامع البيانات المهملة العديد من أحداث جمع البيانات المهملة وقد يؤدي إلى انخفاض مستوى أداء تطبيقك.

للحصول على مزيد من المعلومات العامة عن جمع البيانات غير المرغوب فيها، اطّلِع على جمع البيانات المهملة.

مشاركة الذكريات

سعيًا إلى تلبية كل ما يحتاج إليه نظام Android في ذاكرة الوصول العشوائي (RAM)، يحاول نظام Android مشاركة صفحات RAM على مستوى العمليات. ويمكن إجراء ذلك بالطرق التالية:

  • يتم تقسيم كل عملية تطبيق من عملية حالية تسمى Zygote. تبدأ عملية Zygote عند تشغيل النظام وتحميل رمز إطار العمل والموارد الشائعة (مثل مواضيع الأنشطة). لبدء عملية تطبيق جديدة، يفحص النظام عملية Zygote بعد ذلك ويحمِّل رمز التطبيق ويشغِّله في العملية الجديدة. ويسمح هذا الأسلوب بمشاركة معظم صفحات ذاكرة الوصول العشوائي المخصّصة لرمز إطار العمل والموارد في جميع عمليات التطبيق.
  • تندمج معظم البيانات الثابتة في عملية. تسمح هذه التقنية بمشاركة البيانات بين العمليات، وتتيح أيضًا نشرها على صفحات عند الحاجة. وتشمل أمثلة البيانات الثابتة ما يلي: رمز Dalvik (من خلال وضعه في ملف .odex مرتبط مسبقًا للمشاركة المباشرة)، وموارد التطبيق (من خلال تصميم جدول الموارد على شكل بنية يمكن تعديلها ومن خلال محاذاة إدخالات ZIP لحزمة APK) وعناصر المشروع التقليدية، مثل الرموز البرمجية الأصلية في ملفات .so.
  • وفي العديد من الأماكن، يشارك نظام التشغيل Android ذاكرة الوصول العشوائي الديناميكية نفسها في مختلَف العمليات باستخدام مناطق الذاكرة المشتركة المخصّصة بشكل واضح (إما باستخدام ashmem أو gralloc). على سبيل المثال، تستخدم مساحات عرض النوافذ ذاكرة مشتركة بين التطبيق وأداة إنشاء الشاشة، في حين تستخدم مساحات التخزين المؤقت للمؤشر ذاكرة مشتركة بين موفّر المحتوى والبرنامج.

بسبب الاستخدام المفرط للذاكرة المشتركة، يتطلب تحديد حجم الذاكرة التي يستخدمها تطبيقك العناية. تتم مناقشة أساليب تحديد استخدام التطبيق للذاكرة بشكل صحيح في التحقيق في استخدام ذاكرة الوصول العشوائي.

تخصيص ذاكرة التطبيق واستعادتها

يتم تقييد كومة الذاكرة المؤقتة من Dalvik إلى نطاق ذاكرة افتراضية واحد لكل عملية تطبيق. ويحدّد ذلك حجم الذاكرة المنطقية التي يمكن أن تزيد حسب الحاجة، ولكن إلى الحد الذي يحدّده النظام لكل تطبيق فقط.

وتختلف الحجم المنطقي لكومة الذاكرة المؤقتة باختلاف مقدار الذاكرة الفعلية التي تستخدمها كومة الذاكرة المؤقتة. عند فحص كومة الذاكرة المؤقتة لتطبيقك، يحسب Android قيمة تُسمى "حجم المجموعة التناسبية" (PSS)، والتي تراعي كلاً من الصفحات غير النظيفة والنظيفة التي تتم مشاركتها مع العمليات الأخرى، ولكن بالمقدار الذي يتناسب فقط مع عدد التطبيقات التي تشترك في ذاكرة الوصول العشوائي هذه. وإجمالي الفحص (PSS) هو ما يعتبره النظام مساحة الذاكرة الفعلية. للمزيد من المعلومات حول خدمة PSS، يُرجى الاطّلاع على دليل التحقيق في استخدام ذاكرة الوصول العشوائي (RAM).

ولا يضغط Dalvik على الحجم المنطقي من كومة الذاكرة المؤقتة، أي أنّ نظام التشغيل Android لا يلغي تجزئة كومة الذاكرة المؤقتة لإغلاقها. ولا يستطيع نظام Android تقليص حجم كومة الذاكرة المؤقتة المنطقية إلا في حال عدم توفُّر مساحة غير مُستخدَمة في نهاية كومة الذاكرة المؤقتة. ومع ذلك، سيظل بإمكان النظام تقليل الذاكرة الفعلية التي تستخدمها كومة الذاكرة المؤقتة. بعد جمع البيانات المهملة، يمشي "دالفيك" في كومة الذاكرة المؤقتة ويعثر على الصفحات غير المستخدَمة، ثم يعيد تلك الصفحات إلى النواة باستخدام النصائح. بالتالي، من المفترض أن تؤدي الحصص ومواقع الصفقات المقترنة للأجزاء الكبيرة إلى استعادة كل (أو معظم) الذاكرة الفعلية المستخدمة. ومع ذلك، قد يكون استرداد الذاكرة من التوزيعات الصغيرة أقل فعالية بكثير، لأنّ الصفحة المستخدمة لتخصيص جزء صغير قد تظل تتم مشاركتها مع صفحة أخرى لم يتم إخلاء جزء منها بعد.

تقييد ذاكرة التطبيق

للحفاظ على بيئة فعّالة تتضمّن مهام متعدّدة، يضع Android حدًا صارمًا لحجم الذاكرة لكل تطبيق. ويختلف الحد الدقيق لحجم الذاكرة باختلاف الأجهزة بناءً على حجم ذاكرة الوصول العشوائي المُتاح على الجهاز بشكل عام. إذا وصل تطبيقك إلى سعة كومة الذاكرة المؤقتة وحاول تخصيص المزيد من الذاكرة، يمكنه تلقّي OutOfMemoryError.

في بعض الحالات، قد تحتاج إلى إجراء طلب بحث من النظام لتحديد حجم كومة الذاكرة المؤقتة المتوفّرة لديك بالضبط على الجهاز الحالي، على سبيل المثال لتحديد مقدار البيانات الآمنة للاحتفاظ بها في ذاكرة التخزين المؤقت. يمكنك الاستعلام من النظام عن هذا الرقم من خلال استدعاء getMemoryClass(). تعرِض هذه الطريقة عددًا صحيحًا يشير إلى عدد وحدات الميغابايت المتاحة لكومة الذاكرة البرمجية في تطبيقك.

تبديل التطبيقات

عند التبديل بين التطبيقات، يحتفظ Android بالتطبيقات التي لا تكون في المقدّمة، أي غير مرئية للمستخدم أو يشغّل خدمة تعمل في المقدّمة، مثل تشغيل الموسيقى، في ذاكرة التخزين المؤقت. على سبيل المثال، عندما يشغِّل المستخدم تطبيقًا لأول مرة، يتم إنشاء عملية له، ولكن عندما يغادر المستخدم التطبيق، لا يتم إنهاء هذه العملية. ويحتفظ النظام بالعملية في ذاكرة التخزين المؤقت. وإذا عاد المستخدم إلى التطبيق لاحقًا، سيعيد النظام استخدام العملية، ما يزيد من سرعة تبديل التطبيق.

وإذا كان تطبيقك يتضمّن عملية مخزّنة مؤقتًا ويحتفظ بالموارد التي لا يحتاج إليها حاليًا، يؤثر تطبيقك في الأداء العام للنظام حتى وإن لم يكن المستخدم قيد الاستخدام. ونظرًا لانخفاض موارد النظام، مثل الذاكرة، يؤدي ذلك إلى إنهاء العمليات في ذاكرة التخزين المؤقت. ويراعي النظام أيضًا العمليات التي تحتفظ بأكبر قدر من الذاكرة ويمكن إنهاؤها لإخلاء بعض ذاكرة الوصول العشوائي.

ملاحظة: كلما قلّت مساحة الذاكرة التي يستهلكها تطبيقك أثناء تخزينها في ذاكرة التخزين المؤقت، زادت فرص تعرّضه للإغلاق واستئناف تشغيله بسرعة. ومع ذلك، بناءً على متطلبات النظام الفورية، من الممكن إنهاء العمليات التي تم تخزينها مؤقتًا في أي وقت بغض النظر عن استخدامها للموارد.

لمزيد من المعلومات حول كيفية تخزين العمليات في ذاكرة التخزين المؤقت بدون تشغيلها في المقدّمة وكيفية تحديد Android للعمليات التي قد يتم قتلها، يمكنك الاطّلاع على دليل العمليات وسلاسل المحادثات.

اختبار ضغط الذاكرة

على الرغم من أنّ مشاكل إجهاد الذاكرة أقل شيوعًا على الأجهزة المتطورة، إلا أنّها من الممكن أن تتسبب في حدوث مشاكل لمستخدمي الأجهزة ذات ذاكرة الوصول العشوائي المنخفضة، مثل الأجهزة التي تعمل بنظام التشغيل Android (الإصدار Go). من المهم أن تحاول إعادة إنتاج هذه البيئة التي تعاني من إجهاد في الذاكرة حتى تتمكن من كتابة اختبارات الأدوات للتحقق من سلوك التطبيق وتحسين تجربة المستخدمين على الأجهزة منخفضة الذاكرة.

اختبار التطبيق المرتبط بالإجهاد

اختبار التطبيق المكثف (stressapptest) هو اختبار واجهة للذاكرة يساعد في إنشاء مواقف واقعية ومليئة بالحِمل لاختبار قيود مختلفة على الذاكرة والأجهزة لتطبيقك. وبفضل القدرة على تحديد حدود الذاكرة والوقت، يتيح لك ذلك كتابة الأدوات اللازمة للتحقّق من المصادفة الفعلية التي تكون عالية في الذاكرة. على سبيل المثال، استخدم مجموعة الأوامر التالية لنشر المكتبة الثابتة في نظام ملفات البيانات وجعلها قابلة للتنفيذ، وتشغيل اختبار إجهاد لمدة 20 ثانية من 990 ميغابايت:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

راجِع مستندات stressapptest للحصول على مزيد من المعلومات حول تثبيت الأداة والوسيطات الشائعة ومعلومات معالجة الأخطاء.

ملاحظات حول اختبار الإجهاد

يمكن استخدام أدوات مثل stressapptest لطلب عمليات تخصيص ذاكرة أكبر مما هو متاح مجانًا. وقد يؤدّي هذا النوع من الطلبات إلى إرسال تنبيهات مختلفة يجب أن تكون على دراية بها من جهة التطوير. هناك ثلاثة تنبيهات رئيسية يمكن رفعها بسبب انخفاض مدى توفُّر الذاكرة:
  • SIGABRT: هذا عطل فادح أصلي في عمليتك بسبب طلب تخصيصات بحجم أكبر من الذاكرة الحرة، في حين أن النظام يتعرض لضغط في الذاكرة.
  • SIGQUIT: لعرض تفريغ الذاكرة الأساسي وإنهاء العملية عندما يتم رصدها من خلال اختبار قياس حالة التطبيق.
  • TRIM_MEMORY_EVENTS: تتوفّر عمليات الاستدعاء هذه في الإصدار Android 4.1 (المستوى 16 لواجهة برمجة التطبيقات) والإصدارات الأحدث، وتوفّر تنبيهات تفصيلية للذاكرة للعملية.