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

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

- التطبيقات (Java/Kotlin): يرى المطوّرون في المقام الأول كائنات Java المخصّصة في ذاكرة Java.
- وقت تشغيل Android (ART): يدير وقت تشغيل Android (ART) الذاكرة الديناميكية لـ Java/Kotlin باستخدام صفحات الذاكرة الافتراضية من النواة. كان يُعرف سابقًا باسم Dalvik (يتم استخدام المصطلحين أحيانًا بالتبادل).
- نواة Linux: ترى النواة الذاكرة من حيث الصفحات الفعلية والافتراضية. عادةً ما يكون حجم هذه الصفحات 4 كيلوبايت، ولكن يتيح Android أيضًا استخدام صفحات بحجم 16 كيلوبايت. لا يعرف النواة شيئًا عن "كائنات Java".
إذا أردت تحسين الذاكرة، عليك إما تحسينها ضمن الطبقة الخاصة بك (مثل تحميل عدد أقل من الصور النقطية) أو فهم الطبقات التي تقع أسفلها لمعرفة كيفية ترجمة عمليات التخصيص عالية المستوى إلى استخدام فعلي للصفحة.
أنواع الذاكرة: المجهولة مقابل المدعومة بالملفات
قبل التعمّق في كيفية استعادة نظام التشغيل للذاكرة، عليك فهم الفئتين الأساسيتين لصفحات الذاكرة في Linux:
- الذاكرة المجهولة (
anon): الذاكرة التي لا يتم الاحتفاظ بنسخة احتياطية منها في ملف على مساحة التخزين ويشمل ذلك الذاكرة المخصّصة من خلال لغة C/C++malloc(مثل أداة تخصيص Scudo) والذاكرة المخصّصة لكائنات Java في الذاكرة الديناميكية لـ Java/Kotlin. بما أنّ هذه الذاكرة لا تحتوي على ملف مصدر للرجوع إليه، على نظام التشغيل إما الاحتفاظ بها في ذاكرة الوصول العشوائي (RAM) أو ضغطها ونقلها إلى ZRAM عند الضغط. - الذاكرة المستندة إلى الملفات (
file): الذاكرة التي يتم ربطها مباشرةً من ملف على مساحة التخزين. ويشمل ذلك الرموز البرمجية القابلة للتنفيذ (ملفات DEX ومكتبات.soالمجمّعة من رموز برمجية أصلية) والخطوط.
الملفات المعيّنة في الذاكرة (mmap)
يستخدم Android دالة mmap لربط الذاكرة المجهولة والذاكرة المدعومة بالملفات بمساحة عنوان العملية.
عند التعامل مع الذاكرة المستندة إلى الملفات، يتم تصنيف الصفحات إلى:
- الذاكرة النظيفة: الصفحات التي تم ربطها من ملف لم يتم تعديله إذا احتاج النظام إلى المزيد من الذاكرة، يمكن للنواة ببساطة إسقاط هذه الصفحات، حيث يمكن إعادة تحميلها مباشرة من الملف الموجود على وحدة التخزين لاحقًا.
- الذاكرة غير الصالحة: الصفحات التي تم تعديلها من خلال العملية لا يمكن تجاهل هذه الصفحات، فهي تعمل كذاكرة مجهولة المصدر ويجب الاحتفاظ بها في ذاكرة RAM أو نقلها إلى ZRAM.
يفضّل نظام Android تنسيقات الملفات (مثل DEX) التي يمكن ربطها بالذاكرة، ما يتيح للنظام استعادة الذاكرة النظيفة بسهولة عند الحاجة. بما أنّه لا يمكن ببساطة تجاهل الذاكرة المجهولة والذاكرة غير الصالحة، فإنّ ارتفاع معدّل الذاكرة غير الصالحة أو المجهولة الخاصة هو السبب الرئيسي في حدوث مشكلة التبديل المفرط في النظام وتدخُّل آلية إنهاء العمليات عند انخفاض الذاكرة (LMK). إذا كان تطبيقك يستخدم الكثير من الذاكرة غير الصالحة الخاصة، فأنت تساهم بشكل مباشر في تدهور الأداء.
نموذج عملية Zygote
يقلّل Android من تكلفة بدء تشغيل التطبيقات الجديدة باستخدام عملية تُسمى Zygote.
- يبدأ Zygote عند تشغيل الجهاز ويحمّل مسبقًا فئات وموارد إطار العمل الشائعة في ذاكرته. من منظور النواة، تصبح هذه ذاكرة غير مستخدمة مجهولة المصدر، ولكنها فريدة لعملية Zygote.
- عند بدء تشغيل تطبيق جديد، ينشئ النظام نسخة من عملية Zygote.
- تكتسب العملية الفرعية الجديدة ذاكرة Zygote باستخدام تعيين مشترك مع دلالات النسخ عند الكتابة (COW).

طالما أنّ العملية الفرعية تقرأ فقط الذاكرة المكتسَبة من Zygote، تظل صفحات الذاكرة الفعلية مشتركة بين جميع العمليات. عندما تقوم عملية فرعية بتعديل صفحة مشتركة، تقوم النواة بإنشاء نسخة خاصة من تلك الصفحة للعملية بشكل شفاف. يسمح هذا النموذج للعديد من العمليات بمشاركة جزء كبير من ذاكرتها، خاصةً رمز إطار العمل والموارد، ما يقلّل بشكل كبير من استهلاك الذاكرة الإجمالي للنظام.
RSS وPSS وUSS وmemcg
بما أنّ الذاكرة تتم مشاركتها بشكل كبير بين العمليات، وبشكل أساسي من خلال نموذج Zygote، هناك عدة طرق لحساب استخدام الذاكرة في عملية معيّنة. تحتسب كل من RSS وPSS وUSS الصفحات التي تم ربطها بمساحة عناوين العملية:

- RSS (حجم المجموعة المقيمة): إجمالي عدد الصفحات التي تتضمّنها العملية في ذاكرة الوصول العشوائي (RAM). يؤدي ذلك إلى المبالغة في تقدير الاستخدام لأنّه يتم احتساب الصفحات المشتركة عدة مرات (مرة واحدة لكل عملية تشاركها).
- حجم المجموعة المتناسب (PSS): إجمالي مقدار الذاكرة الفريدة للعملية، بالإضافة إلى حصتها النسبية من الذاكرة المشتركة إذا تمت مشاركة صفحة من خلال 5 عمليات، يتم تحصيل رسوم من كل عملية مقابل 1/5 من تلك الصفحة.
- USS (حجم المجموعة الفريدة): مقدار الذاكرة الفريد للعملية. هذه هي الذاكرة التي ستتم إعادتها إلى النظام في حال إنهاء العملية.
memcg: تدوين سجلات الاستخدام الخاصة بالنواة
تحتفظ النواة أيضًا بإجمالي الذاكرة التي تستخدمها كل عملية، سواء تم ربط هذه الذاكرة أم لا. على Android، تنتمي كل عملية تطبيق إلى مجموعة التحكّم في الذاكرة (memcg) الخاصة بها، والمدمجة داخل مجموعة التحكّم في الذاكرة (memcg) للتطبيق بأكمله (يُرجى الاطّلاع على التسلسل الهرمي لمجموعة التحكّم في الذاكرة (memcg) على Android).
عند تخصيص صفحة من الذاكرة، يُحمِّل النواة هذه الصفحة إلى memcg
العملية التي خصصتها. يتم تسجيل إجمالي الرسوم في ملف memory.current
الخاص بـ memcg.
يختلف احتساب memcg عن RSS وPSS وUSS في بعض الجوانب المهمة:
- يتم احتساب كل صفحة مرة واحدة بالضبط، في memcg للعملية التي أدخلتها لأول مرة في الذاكرة. وتظل الرسوم هناك حتى إذا قامت عمليات أخرى بتعيين الصفحة نفسها لاحقًا. يتم تحميل الصفحات التي يحمّلها Zygote مسبقًا إلى Zygote، وليس إلى تطبيقك. عندما يكتب تطبيقك في إحدى هذه الصفحات، يتم تحميل النسخة الخاصة إلى تطبيقك.
- وتشمل الذاكرة التي لا يمكن لمقاييس جدول الصفحات رصدها.
memory.currentيحتسب صفحات الملفات التي يقرأها تطبيقك بدون ربطها (على سبيل المثال، صفحات قاعدة البيانات التي تتم قراءتها باستخدامreadأوpread). - ويتم تتبُّع عمليات التبديل بشكل منفصل. يتم نقل الذاكرة المضغوطة في ZRAM من
memory.currentإلىmemory.swap.current، حتى تتمكّن من رؤية كليهما بدون تخمين. - قراءتها غير مكلفة. تكون العدّادات محدّثة دائمًا، ولا تؤدي قراءتها إلى قفل مساحة عنوان تطبيقك أو فحصها.
- وهي ما يحدّده النظام. إنّ الحدود القصوى للذاكرة وميزانيات الذاكرة التي يطبّقها Android على التطبيقات تؤثر في رسوم memcg.
لا يتم عادةً احتساب ذاكرة الرسومات، مثل المخازن المؤقتة وراء الصور النقطية للأجهزة، ضمن مجموعة التحكم في الذاكرة (memcg) الخاصة بتطبيقك.
ما هي المقاييس التي يجب استخدامها ومتى؟
يعتمد اختيار مقياس الذاكرة المناسب على ما تحاول قياسه والبيئة التي تقيسه فيها. على الأجهزة التي تعمل بالإصدار Android 17 QPR2 (المستوى 37.2 من واجهة برمجة التطبيقات) والإصدارات الأحدث، ابدأ باستخدام memcg عند تتبُّع تطبيقك.
| المقياس | متوافق مع: | قوة المفتاح | العيب الرئيسي |
|---|---|---|---|
| memcg | تتبُّع تطبيق معيّن بمرور الوقت ومقارنته بالميزانيات | يتم احتساب كل صفحة مرة واحدة، ويشمل ذلك ذاكرة التخزين المؤقت للصفحات غير المرتبطة، ويتم تتبُّع مساحة التخزين المؤقت بشكل منفصل. رخيصة للقراءة. | لا تتوفّر هذه الميزة على جميع الأجهزة التي تعمل بإصدارات Android 17 QPR2 والإصدارات الأقدم. يتم احتساب الصفحات المشتركة ضمن العملية التي استخدمتها أولاً. |
| PSS | لقطات الذاكرة على مستوى النظام | يتم تقسيم الذاكرة المشتركة بدقة. ويساوي مجموع PSS إجمالي الذاكرة المستخدَمة. | تتفاوت هذه النسبة استنادًا إلى العمليات الأخرى قيد التشغيل. غير مناسب لمقارنة تطبيق معيّن بمرور الوقت |
| RSS | تتبُّع تطبيق معيّن بمرور الوقت، والقياس عن بُعد في المجال | مستقر ومستقل عن العمليات الأخرى يمكن جمعها بسرعة وبتكلفة منخفضة. | يُفرط في تقدير إجمالي الاستخدام لأنّه يحتسب الذاكرة المشتركة عدة مرات. |
| USS | تقييم تأثير إنهاء عملية | يعرض مقدار الذاكرة التي سيتم إخلاؤها بالضبط في حال إنهاء التطبيق. | يتجاهل الذاكرة المشتركة تمامًا. |
memcg (رسوم مجموعة التحكّم في الذاكرة)
- الاستخدام الأمثل: تتبُّع استخدام الذاكرة في تطبيق فردي بمرور الوقت، أو في رحلات المستخدم الأساسية (CUJ)، أو بين إصدارات التطبيق، على الأجهزة التي تعمل بنظام Android 17 QPR2 أو الإصدارات الأحدث. وهو أيضًا المقياس الذي يجب مراقبته عند معايرة ميزانيات الذاكرة، لأنّه الرقم الذي تحدّده الميزانيات.
- السبب: مثل RSS، لا يتغيّر حجم memcg charge عندما تبدأ التطبيقات الأخرى مشاركة الصفحات مع تطبيقك أو تتوقف عن ذلك. وعلى عكس RSS، لا يتم احتساب الصفحة مرتين، لذا يمكنك جمع حجم جميع العمليات التي يتم تنفيذها في تطبيقك. يمكن أن تكون قيمة memcg للتطبيق بأكمله أعلى من هذا المجموع، لأنّها تحتفظ بصفحات الملفات المخزّنة مؤقتًا من العمليات التي تم الخروج منها إلى أن يستعيدها النواة. ويحتسب أيضًا الذاكرة التي لا تحتسبها RSS وPSS وUSS، مثل ذاكرة التخزين المؤقت للملفات غير المعيّنة. قراءتها رخيصة، لأن النواة تقوم بتحديث العدادات أثناء شحن الصفحات.
- كيفية قراءة هذه البيانات: اقرأ
memory.currentوmemory.swap.currentوmemory.statفي دليل memcg لإحدى العمليات (راجِع إحصاءات memcg). في الإصدار Android 18 والإصدارات الأحدث، يطبعها أيضًاdumpsys meminfo. من داخل تطبيقك، استخدِمMemoryBudgetManager. - أفضل ممارسات قياس الحقول: للأسباب نفسها الموضّحة في
RSS، يمكنك تتبُّع الذاكرة المجهولة + مساحة الإبدال: الحقل
anonفيmemory.statبالإضافة إلىmemory.swap.current. يتضمّن الحقلfileفيmemory.statصفحات الملفات التي قرأها تطبيقك مرة واحدة ولم تستردها النواة بعد، لذا يمكن أن يظلmemory.currentمرتفعًا لفترة من الوقت بعد أن يتوقف تطبيقك عن استخدام هذه الذاكرة. - حالات توخّي الحذر: بما أنّ الصفحات يتم تحميلها على العملية التي
تصل إليها أولاً، يتم تحميل تطبيقك على صفحة ملف مشترَكة (مثل
خط أو مكتبة مشترَكة) يقرأها قبل أي عملية أخرى.
في المقابل، قد يتم تحميل عملية أخرى تكلفة الصفحات التي يستخدمها تطبيقك أيضًا. يُرجى توقُّع اختلافات بسيطة بين الأجهزة وعمليات التشغيل للذاكرة المستندة إلى الملفات، والاعتماد على
anonوالمبادلة عند المقارنة.
PSS (حجم المجموعة المتناسب)
- الاستخدام الأمثل: التقاط لقطة كاملة لذاكرة النظام لجهاز معيّن في وقت محدّد
- السبب: يتضمّن مقياس PSS خاصية حسابية مفيدة، وهي أنّ مجموع قيم PSS لجميع العمليات الجارية يساوي إجمالي الذاكرة التي تستخدمها العمليات في النظام. ويوزّع الذاكرة المشتركة بشكل مثالي بدون احتسابها مرّتين.
- حالات تجنُّب الاستخدام: يجب عدم استخدام PSS لمقارنة عملية معيّنة في لقطتَين مختلفتَين أو في أوقات مختلفة أو على أجهزة مختلفة. بما أنّ حجم المشاركة النسبي يعتمد على عدد العمليات الأخرى التي تشارك حاليًا الصفحات مع عمليتك، يمكن أن يتفاوت حجم المشاركة النسبي لتطبيقك حتى إذا ظلّ السلوك الفعلي لتطبيقك بدون تغيير تمامًا.
RSS (حجم المجموعة المقيمة)
- الاستخدام الأمثل: تتبُّع استخدام الذاكرة في تطبيق فردي بمرور الوقت، على مستوى رحلات المستخدم الأساسية المختلفة، أو المقارنة بين إصدارات التطبيق. وهو أيضًا الخيار الأكثر عملية لقياس البيانات عن بُعد في الميدان.
- السبب: مقياس RSS أكثر ثباتًا من مقياس PSS. وتخبرك عن الذاكرة التي تم تعيينها في عمليتك بغض النظر عما تفعله التطبيقات الأخرى. علاوة على ذلك، فإن RSS رخيص في الجمع. يتطلّب قياس PSS أو USS فحص هياكل إدارة الذاكرة المعقّدة في النواة والحصول على أقفال، ما قد يؤدي إلى حدوث مشاكل في أداء النظام (مثل بطء واجهة المستخدم) إذا تم جمعها بشكل متكرّر.
- أفضل الممارسات المتعلّقة بالقياس الميداني: عند جمع البيانات من الأجهزة في
البيئة الفعلية، من الأفضل عادةً قياس RSS مجهول الهوية + مساحة التخزين المؤقت (ZRAM).
- لماذا تظهر البيانات كمجهولة المصدر فقط؟ يمكن للنواة إزالة الصفحات المدعومة بالملفات (مثل الرموز البرمجية أو موارد التطبيقات أو الخطوط أو الملفات الأخرى التي تم ربطها بالذاكرة) في أي وقت بسبب الضغط على ذاكرة التخزين المؤقت للصفحات على مستوى النظام. إنّ تضمين صفحات الملفات في المقاييس لتتبُّع تطبيق معيّن يؤدي إلى حدوث تشويش من قرارات إدارة الذاكرة في نظام التشغيل التي قد تكون ناتجة عن ضغط الذاكرة من تطبيقات أخرى أو من النظام. في المقابل، يتحكّم تطبيقك مباشرةً في الذاكرة المجهولة (مثل كومة ذاكرة Java وكومة الذاكرة الأصلية).
- لماذا يجب إضافة مساحة تخزين مؤقت؟ عندما يكون عبء الذاكرة مرتفعًا، يضغط نظام التشغيل الصفحات المجهولة وينقلها إلى ZRAM (مساحة الإبدال). إذا كنت تقيس فقط الذاكرة المجهولة المصدر المقيمة، قد تُظهر المقاييس بشكل خاطئ "انخفاضًا" في استخدام الذاكرة لمجرد أنّ النظام كان تحت الضغط وتم تبديل صفحاتك. تضمن إضافة الذاكرة المتبادلة احتساب كل الذاكرة المجهولة التي خصّصها تطبيقك، سواء كانت موجودة حاليًا في ذاكرة الوصول العشوائي (RAM) أو ZRAM.
- يُفضَّل استخدام memcg عندما يكون ذلك ممكنًا. على الأجهزة التي تعمل بنظام التشغيل Android 17 QPR2 والإصدارات الأحدث، يمنحك
anonفيmemory.statبالإضافة إلىmemory.swap.currentقياسًا مشابهًا يستبعد الصفحات المجهولة التي تمت مشاركتها مع Zygote، ويكون جمعها أقل تكلفة (راجِع memcg).
USS (حجم المجموعة الفريدة)
- الاستخدام الأمثل: تحديد التأثير الفوري في النظام عند إنهاء عملية معيّنة
- السبب: تمثّل مجموعة USS بدقة مقدار الذاكرة التي سيتم استردادها وإعادتها إلى النظام فورًا في حال إنهاء العملية (على سبيل المثال، من خلال برنامج Low Memory Killer الخفي،
lmkd). - طريقة الاستخدام: يُعدّ هذا المقياس مفيدًا للغاية عند تقييم العمليات المستمرة أو الخدمات التي تُشغَّل في الخلفية. إذا كنت تقيّم تكلفة عملية في الخلفية على مستوى النظام، فإنّ USS يخبرك بالضبط بمقدار الذاكرة التي يضحي بها النظام حصريًا للحفاظ على استمرار هذه العملية المحددة.