تسرب الذاكرة
في علم الحاسوب ، يُعدّ تسرب الذاكرة نوعًا من أنواع تسرب الموارد ، ويحدث عندما يُدير برنامج الحاسوب تخصيصات الذاكرة بشكل خاطئ [ 1 ] بحيث لا يتم تحرير الذاكرة التي لم تعد مطلوبة. وقد يحدث تسرب الذاكرة أيضًا عندما يتم تخزين كائن في الذاكرة ولكن لا يمكن الوصول إليه من قِبل الكود قيد التشغيل (أي ذاكرة غير قابلة للوصول ). [ 2 ] تتشابه أعراض تسرب الذاكرة مع أعراض عدد من المشكلات الأخرى، ولا يمكن تشخيصها عمومًا إلا من قِبل مبرمج لديه إمكانية الوصول إلى شفرة المصدر للبرنامج.
ومن المفاهيم ذات الصلة "تسرب المساحة"، وهو عندما يستهلك البرنامج ذاكرة زائدة ولكنه يحررها في النهاية. [ 3 ]
نظراً لقدرتها على استنفاد ذاكرة النظام المتاحة أثناء تشغيل التطبيق، فإن تسريبات الذاكرة غالباً ما تكون سبباً أو عاملاً مساهماً في تقادم البرامج .
الآثار
تسريبات طفيفة
إذا كان البرنامج يعاني من تسرب للذاكرة، وكان استهلاكه للذاكرة يتزايد باستمرار، فلن تظهر أعراض فورية في العادة. في أنظمة التشغيل الحديثة، تُحرر الذاكرة المستخدمة فعليًا من قِبل التطبيق عند إغلاقه. هذا يعني أن تسرب الذاكرة في برنامج يعمل لفترة قصيرة قد لا يُلاحظ، ونادرًا ما يكون خطيرًا، كما يمكن إخفاء التسريبات البطيئة بإعادة تشغيل البرنامج. لكل نظام مادي مقدار محدود من الذاكرة، وإذا لم يُعالج تسرب الذاكرة (على سبيل المثال، بإعادة تشغيل البرنامج المُسرِّب)، فسيؤدي ذلك في النهاية إلى مشاكل للمستخدمين. [ 4 ]
الضرب
تحتوي معظم أنظمة تشغيل أجهزة الكمبيوتر المكتبية الحديثة على ذاكرة رئيسية ، موجودة فعليًا في رقائق ذاكرة الوصول العشوائي (RAM)، ووحدة تخزين ثانوية مثل القرص الصلب . يتم تخصيص الذاكرة بشكل ديناميكي ، حيث يحصل كل برنامج على مقدار الذاكرة الذي يحتاجه. تُنقل الصفحات النشطة إلى الذاكرة الرئيسية للوصول السريع، بينما تُنقل الصفحات غير النشطة إلى وحدة التخزين الثانوية لإفساح المجال، حسب الحاجة. عندما يبدأ برنامج واحد باستهلاك كمية كبيرة من الذاكرة، فإنه عادةً ما يشغل مساحة متزايدة من الذاكرة الرئيسية، مما يدفع البرامج الأخرى إلى وحدة التخزين الثانوية ، وهو ما يؤدي عادةً إلى تباطؤ ملحوظ في أداء النظام. حتى في حال إنهاء البرنامج المُستهلك للذاكرة، قد يستغرق الأمر بعض الوقت حتى تعود البرامج الأخرى إلى الذاكرة الرئيسية، وحتى يعود الأداء إلى طبيعته. يُعرف هذا التباطؤ الناتج عن الاستخدام المفرط لوحدة التخزين الثانوية باسم " التبديل المُفرط" .
حالة نفاد الذاكرة
إذا استهلك برنامجٌ ما كامل الذاكرة المتاحة قبل إغلاقه (سواءً كانت ذاكرةً افتراضيةً أو ذاكرةً رئيسيةً فقط، كما هو الحال في الأنظمة المدمجة)، فإن أي محاولة لتخصيص المزيد من الذاكرة ستفشل. عادةً ما يؤدي هذا إلى إنهاء البرنامج الذي يحاول تخصيص الذاكرة، أو إلى حدوث خطأ تجزئة . صُممت بعض البرامج للتعافي من هذه الحالة (ربما باللجوء إلى الذاكرة المحجوزة مسبقًا). قد يكون البرنامج الأول الذي يواجه مشكلة نفاد الذاكرة هو البرنامج الذي يعاني من تسرب الذاكرة، وقد لا يكون.
تتضمن بعض أنظمة التشغيل متعددة المهام آليات خاصة للتعامل مع نفاد الذاكرة، مثل إنهاء العمليات عشوائيًا (مما قد يؤثر على العمليات "غير الضارة")، أو إنهاء أكبر عملية في الذاكرة (والتي يُفترض أنها سبب المشكلة). كما تفرض بعض أنظمة التشغيل حدًا أقصى للذاكرة المخصصة لكل عملية، لمنع أي برنامج من الاستحواذ على كامل ذاكرة النظام. لكن يعيب هذا الترتيب أنه قد يتطلب أحيانًا إعادة تهيئة نظام التشغيل لضمان التشغيل السليم للبرامج التي تتطلب بالفعل كميات كبيرة من الذاكرة، مثل تلك التي تتعامل مع الرسومات أو الفيديو أو العمليات الحسابية العلمية.
إذا كان تسرب الذاكرة في نواة النظام ، فمن المرجح أن يتعطل نظام التشغيل نفسه. وقد تتعطل أجهزة الكمبيوتر التي تفتقر إلى إدارة متطورة للذاكرة، مثل الأنظمة المدمجة، بشكل كامل نتيجة لتسرب ذاكرة مستمر.
أسباب التسريبات الخطيرة
وتشمل التسريبات الأكثر خطورة تلك التي:
- يستمر تشغيل البرنامج لفترة طويلة ويستهلك ذاكرة إضافية بمرور الوقت، مثل مهام الخلفية على الخوادم، وخاصة في الأنظمة المدمجة التي قد تُترك قيد التشغيل لسنوات عديدة.
- يتم تخصيص ذاكرة جديدة بشكل متكرر للمهام التي تُنفذ لمرة واحدة، مثل عرض إطارات لعبة كمبيوتر أو فيديو متحرك.
- يمكن للبرنامج أن يطلب ذاكرة، مثل الذاكرة المشتركة ، التي لا يتم تحريرها حتى عند انتهاء البرنامج.
- تكون الذاكرة محدودة للغاية، كما هو الحال في الأنظمة المدمجة أو الأجهزة المحمولة، أو عندما يتطلب البرنامج كمية كبيرة جدًا من الذاكرة في البداية، مما يترك هامشًا ضئيلًا للتسربات.
- يحدث تسرب داخل نظام التشغيل أو مدير الذاكرة
- يتسبب برنامج تشغيل جهاز النظام في حدوث تسريب
- لا يقوم نظام التشغيل بتحرير الذاكرة تلقائيًا عند إنهاء البرنامج
مشاكل البرمجة
تُعدّ تسريبات الذاكرة خطأً شائعًا في البرمجة، خاصةً عند استخدام لغات تفتقر إلى خاصية جمع البيانات المهملة التلقائية ، مثل لغتي C و C++ . عادةً ما يحدث تسريب الذاكرة نتيجةً لعدم إمكانية الوصول إلى جزء من الذاكرة المُخصصة ديناميكيًا . وقد أدّى انتشار أخطاء تسريب الذاكرة إلى تطوير العديد من أدوات تصحيح الأخطاء للكشف عن الذاكرة غير القابلة للوصول. ومن أشهر أدوات تصحيح أخطاء الذاكرة لبرامج C وC++: BoundsChecker و Deleaker وMemory Validator و IBM Rational Purify و Valgrind و Parasoft Insure++ و Dr. Memory و memwatch . يُمكن إضافة خاصية جمع البيانات المهملة "المحافظة" إلى أي لغة برمجة تفتقر إليها كميزة مُدمجة، وتتوفر مكتبات لهذا الغرض لبرامج C وC++. يكتشف جامع البيانات المهملة المحافظ معظم الذاكرة غير القابلة للوصول ويستعيدها، ولكن ليس كلها.
على الرغم من قدرة مدير الذاكرة على استعادة الذاكرة غير القابلة للوصول، إلا أنه لا يستطيع تحرير الذاكرة التي لا تزال قابلة للوصول، وبالتالي لا تزال قابلة للاستخدام. لذا، توفر مديرات الذاكرة الحديثة تقنيات للمبرمجين لتمييز الذاكرة دلاليًا بمستويات فائدة متفاوتة، تتوافق مع مستويات وصول متفاوتة . لا يُحرر مدير الذاكرة كائنًا قابلًا للوصول إليه بقوة. يُعتبر الكائن قابلًا للوصول إليه بقوة إذا كان الوصول إليه ممكنًا إما مباشرةً عبر مرجع قوي أو بشكل غير مباشر عبر سلسلة من المراجع القوية. ( المرجع القوي هو مرجع، على عكس المرجع الضعيف ، يمنع جمع الكائن بواسطة جامع البيانات المهملة). ولمنع ذلك، يتحمل المطور مسؤولية تنظيف المراجع بعد استخدامها، عادةً عن طريق تعيين قيمة المرجع إلى null بمجرد انتهاء الحاجة إليه، وإذا لزم الأمر، عن طريق إلغاء تسجيل أي مستمعي أحداث يحتفظون بمراجع قوية للكائن.
بشكل عام، تُعدّ إدارة الذاكرة التلقائية أكثر كفاءةً وملاءمةً للمطورين، إذ لا يحتاجون إلى تنفيذ إجراءات تحرير الذاكرة أو القلق بشأن ترتيب عمليات التنظيف أو التحقق من وجود مرجع لكائن ما. فمن الأسهل على المبرمج معرفة متى لم يعد المرجع مطلوبًا من معرفة متى لم يعد الكائن مُشارًا إليه. مع ذلك، قد تُؤثر إدارة الذاكرة التلقائية سلبًا على الأداء، ولا تُزيل جميع أخطاء البرمجة التي تُسبب تسرب الذاكرة.
استغلال
تُعدّ الأنظمة المتاحة للعامة، مثل خوادم الويب أو أجهزة التوجيه، عرضةً لهجمات حجب الخدمة إذا اكتشف المهاجم سلسلة من العمليات التي يمكن أن تُسبب تسريبًا للبيانات. تُعرف هذه السلسلة باسم " الاستغلال" .
RAII
يُعدّ RAII (استحواذ الموارد أثناء التهيئة ) أسلوبًا شائعًا لحلّ هذه المشكلة في لغات C++ و D و Ada . يتضمن هذا الأسلوب ربط الكائنات ذات النطاق المحدد بالموارد المكتسبة، وتحرير هذه الموارد تلقائيًا بمجرد خروج الكائنات من نطاقها. وعلى عكس جمع البيانات المهملة، يتميز RAII بمعرفة متى تكون الكائنات موجودة ومتى لا تكون. قارن الأمثلة التالية بلغة C وC++:
شركة:
#include <stdlib.h>void someOperation ( int * a ) { // ... }void f ( int n ) { int * a = ( int * ) calloc ( n , sizeof ( int )); someOperation ( a ); free ( a ); }في لغة C++:
استيراد std ؛باستخدام std :: vector ;void someOperation ( vector < int >&a a ) { // ... }void f ( int n ) { vector < int > a ( n ); someOperation ( a ); }يتطلب إصدار C، كما هو مُطبق في المثال، تحريرًا صريحًا للذاكرة؛ حيث يتم تخصيص المصفوفة ديناميكيًا (من الذاكرة الديناميكية في معظم تطبيقات C)، وتستمر في الوجود حتى يتم تحريرها صراحةً. (لاحظ أنه على عكس malloc()، calloc()يقوم بتهيئة جميع العناصر إلى 0.)
لا يتطلب إصدار C++ تحريرًا صريحًا للذاكرة؛ إذ يتم ذلك تلقائيًا بمجرد aخروج الكائن من نطاق التعريف، حتى في حالة حدوث استثناء. وهذا يُجنّبنا بعضًا من أعباء آليات جمع البيانات المهملة . ولأن مُدمّرات الكائنات قادرة على تحرير موارد أخرى غير الذاكرة، فإن RAII يُساعد في منع تسريب موارد الإدخال والإخراج التي يتم الوصول إليها عبر مُعرّف ، والتي لا تتعامل معها آلية جمع البيانات المهملة القائمة على التحديد والمسح بكفاءة. وتشمل هذه الموارد الملفات المفتوحة، والنوافذ المفتوحة، وإشعارات المستخدم، والكائنات في مكتبة رسومات، وعناصر مزامنة الخيوط مثل المقاطع الحرجة، واتصالات الشبكة، والاتصالات بسجل ويندوز أو قاعدة بيانات أخرى.
مع ذلك، فإن استخدام RAII بشكل صحيح ليس بالأمر السهل دائمًا، وله عيوبه. على سبيل المثال، إذا لم يتوخَّ المرء الحذر، فمن الممكن إنشاء مؤشرات (أو مراجع) معلقة عن طريق إرجاع البيانات بالمرجع، ليتم حذف تلك البيانات عند خروج الكائن الحاوي لها من نطاق التعريف.
يستخدم D مزيجًا من RAII وجمع البيانات المهملة، حيث يستخدم التدمير التلقائي عندما يكون من الواضح أنه لا يمكن الوصول إلى كائن خارج نطاقه الأصلي، وجمع البيانات المهملة بخلاف ذلك.
عدّ المراجع والمراجع الدورية
تعتمد معظم أنظمة جمع البيانات المهملة الحديثة على مفهوم إمكانية الوصول ؛ فإذا لم يكن لديك مرجع قابل للاستخدام إلى الذاكرة المعنية، يُمكن جمعها. بينما تعتمد أنظمة أخرى على عدّ المراجع ، حيث يكون الكائن مسؤولاً عن تتبع عدد المراجع التي تشير إليه. إذا انخفض العدد إلى الصفر، يُفترض أن يُحرر الكائن نفسه ويسمح باستعادة ذاكرته. يكمن عيب هذا النموذج في عدم قدرته على التعامل مع المراجع الدورية، ولهذا السبب يُفضل معظم المبرمجين اليوم استخدام أنظمة "التحديد والمسح" الأكثر تكلفة.
يوضح كود Visual Basic التالي تسرب الذاكرة القياسي الناتج عن عد المراجع:
Dim A , B Set A = CreateObject ( "Some.Thing" ) Set B = CreateObject ( "Some.Thing" ) ' عند هذه النقطة، يكون لكل من الكائنين مرجع واحد،Set A . member = B Set B . member = A ' الآن لكل منهما مرجعان.المجموعة أ = لا شيء ' لا يزال بإمكانك الخروج من هذا الوضع...Set B = Nothing ' والآن لديك تسرب للذاكرة!نهايةعملياً، يمكن رصد هذا المثال البسيط وتصحيحه فوراً. أما في معظم الأمثلة الواقعية، فتمتد سلسلة المراجع لأكثر من عنصرين، ما يجعل اكتشافها أكثر صعوبة.
برز مثال معروف لهذا النوع من التسريب مع ظهور تقنيات برمجة AJAX في متصفحات الويب ، وتحديدًا مشكلة المستمع المنتهي الصلاحية . فقد كان كود جافا سكريبت الذي يربط عنصر DOM بمعالج أحداث، ويفشل في إزالة المرجع قبل الخروج، يتسبب في تسريب الذاكرة (تحتفظ صفحات الويب التي تستخدم AJAX بعنصر DOM معين لفترة أطول بكثير من صفحات الويب التقليدية، لذا كان هذا التسريب أكثر وضوحًا).
الكشف الخارجي

قد يشير نمط استخدام الذاكرة المتذبذب إلى وجود تسريب للذاكرة داخل التطبيق، خاصةً إذا تزامنت الانخفاضات الحادة مع إعادة تشغيل التطبيق. مع ذلك، يجب توخي الحذر لأن نقاط تجميع البيانات المهملة قد تتسبب أيضًا في ظهور هذا النمط، مما يدل على استخدام سليم للذاكرة المخصصة.
لا يُعدّ تزايد استخدام الذاكرة باستمرار دليلاً قاطعاً على وجود تسريب للذاكرة. فبعض التطبيقات تخزن كميات متزايدة من المعلومات في الذاكرة (مثل ذاكرة التخزين المؤقت ). وإذا ما ازداد حجم ذاكرة التخزين المؤقت لدرجة تسبب مشاكل، فقد يكون ذلك خطأً برمجياً أو تصميمياً، ولكنه ليس تسريباً للذاكرة لأن المعلومات تبقى قيد الاستخدام اسمياً. في حالات أخرى، قد تتطلب البرامج كمية كبيرة من الذاكرة بشكل غير معقول لأن المبرمج افترض أن الذاكرة كافية دائماً لمهمة معينة؛ على سبيل المثال، قد يبدأ معالج ملفات الرسومات بقراءة محتويات ملف الصورة بالكامل وتخزينها في الذاكرة، وهو أمر غير عملي عندما تتجاوز الصورة الكبيرة جداً الذاكرة المتاحة. ويتطلب التأكد من أن الاستخدام المفرط للذاكرة ناتج عن تسريب للذاكرة الوصول إلى شفرة البرنامج.
أمثلة
الشفرة الزائفة
يهدف المثال التالي، المكتوب بلغة شبه برمجية ، إلى توضيح كيفية حدوث تسرب الذاكرة وآثاره، دون الحاجة إلى أي معرفة برمجية. البرنامج في هذه الحالة جزء من برنامج بسيط مصمم للتحكم في المصعد . يتم تشغيل هذا الجزء من البرنامج كلما ضغط أي شخص داخل المصعد على زر الطابق المطلوب.
عند الضغط على زر: احصل على بعض الذاكرة، والتي ستُستخدم لتذكر رقم الطابق ضع رقم الطابق في الذاكرة هل وصلنا بالفعل إلى الحد الأدنى المستهدف؟ إذا كان الأمر كذلك، فلا يوجد ما نفعله: انتهى الأمر خلاف ذلك: انتظر حتى يتوقف المصعد عن العمل اذهب إلى الطابق المطلوب تخلص من الذاكرة التي كنا نستخدمها لتذكر رقم الطابق
يحدث تسرب الذاكرة إذا كان رقم الطابق المطلوب هو نفس الطابق الذي يوجد فيه المصعد؛ حيث يتم تجاوز شرط تحرير الذاكرة. وفي كل مرة يحدث فيها هذا، يتسرب المزيد من الذاكرة.
لا تُحدث مثل هذه الحالات عادةً أي آثار فورية. فنادرًا ما يضغط الناس على زر الطابق الذي هم فيه، وعلى أي حال، قد يكون لدى المصعد ذاكرة احتياطية كافية تسمح بحدوث ذلك مئات أو آلاف المرات. مع ذلك، ستنفد ذاكرة المصعد في نهاية المطاف. قد يستغرق هذا شهورًا أو سنوات، لذا قد لا يُكتشف الأمر رغم إجراء اختبارات شاملة.
ستكون العواقب وخيمة؛ فعلى أقل تقدير، سيتوقف المصعد عن الاستجابة لطلبات الانتقال إلى طابق آخر (كما يحدث عند محاولة استدعاء المصعد أو عندما يكون شخص ما بالداخل ويضغط على أزرار الطابق). وإذا احتاجت أجزاء أخرى من البرنامج إلى ذاكرة (كجزء مُخصّص لفتح وإغلاق الباب، على سبيل المثال)، فلن يتمكن أحد من الدخول، وإذا كان هناك شخص بالداخل، فسيُحاصر (بافتراض عدم إمكانية فتح الأبواب يدويًا).
يستمر تسرب الذاكرة حتى إعادة تشغيل النظام. على سبيل المثال: إذا انقطع التيار الكهربائي عن المصعد أو حدث انقطاع في التيار، سيتوقف البرنامج عن العمل. عند عودة التيار، سيُعاد تشغيل البرنامج وستتوفر جميع الذاكرة مرة أخرى، ولكن عملية تسرب الذاكرة البطيئة ستُعاد مع البرنامج، مما قد يُؤثر سلبًا على التشغيل السليم للنظام.
يمكن تصحيح التسريب في المثال أعلاه عن طريق إخراج عملية "الإصدار" من الشرط:
عند الضغط على زر: احصل على بعض الذاكرة، والتي ستُستخدم لتذكر رقم الطابق ضع رقم الطابق في الذاكرة هل وصلنا بالفعل إلى الحد الأدنى المستهدف؟ وإلا: انتظر حتى يتوقف المصعد عن العمل اذهب إلى الطابق المطلوب تخلص من الذاكرة التي كنا نستخدمها لتذكر رقم الطابق
لغة سي++
تقوم دالة C++ التالية بتسريب الذاكرة عمداً عن طريق فقدان المؤشر إلى الذاكرة المخصصة.
void causeLeak () { int * a = new int [ 5 ]; a = nullptr ; /** * لم يعد المؤشر الموجود في 'a' موجودًا، وبالتالي لا يمكن تحريره، * ولكن النظام لا يزال يخصص الذاكرة. * إذا استمر البرنامج في إنشاء مثل هذه المؤشرات دون تحريرها، * فسوف يستهلك الذاكرة باستمرار. * لذلك، سيحدث تسريب. * يجب أن يتطابق الحذف المقابل مع استدعاء جديد. */ }انظر أيضاً
- تجاوز سعة المخزن المؤقت
- إدارة الذاكرة
- مصحح أخطاء الذاكرة
- Plumbr هي أداة شائعة للكشف عن تسرب الذاكرة للتطبيقات التي تعمل على آلة جافا الافتراضية .
- nmon (اختصار لـ Nigel's Monitor) هي أداة مراقبة نظام شائعة لأنظمة التشغيل AIX و Linux.
مراجع
- ↑ كروكفورد، دوغلاس. "تسريبات الذاكرة في جافا سكريبت" . مؤرشف من الأصل في 7 ديسمبر 2012. تم الاطلاع عليه في 20 يوليو 2022 .
- ↑ "التسبب في تسرب الذاكرة باستخدام جافا" . موقع ستاك أوفرفلو . تم الاطلاع عليه بتاريخ 14-06-2013 .
- ↑ ميتشل، نيل. "الفضاء المتسرب" . تم الاطلاع عليه بتاريخ 27 مايو 2017 .
- ↑ رودافشاني، معصومة، وبول إيه إس وارد. "LeakSpot: اكتشاف وتشخيص تسربات الذاكرة في تطبيقات جافا سكريبت." البرمجيات، الممارسة والخبرة 47.1 (2017): 97-123. على الإنترنت.
روابط خارجية
- برنامج Visual Leak Detector، مؤرشف بتاريخ 15 ديسمبر 2015 على موقع Wayback Machine ، وهو برنامج مفتوح المصدر خاص بـ Visual Studio
- فالغريند ، مفتوح المصدر
- Deleaker for Visual Studio، برنامج احتكاري
- أداة التحقق من الذاكرة لبرامج Visual Studio و Delphi و Fortran و Visual Basic، وهي ملكية خاصة.
- اكتشاف تسرب الذاكرة (باستخدام دعم تصحيح الأخطاء في MFC)
- مقال بعنوان " الكشف عن تسرب الذاكرة في الأنظمة المدمجة " بقلم كال إريكسون
- برنامج WonderLeak ، وهو برنامج تحليل أداء عالي لتخصيص الذاكرة ومعالجتها في نظام ويندوز، وهو برنامج خاص.
- أخطاء برمجية
- شذوذ البرمجيات
- إدارة الذاكرة
