نشت حافظه

نشت حافظه (در علوم کامپیوتر) زمانی اتفاق می‌افتد که یک برنامه، حافظه‌ای از سیستم عامل دریافت کند ولی قادر به آزاد کردن و برگرداندن آن نباشد. کمبود حافظه خود نشانه‌ای بر وجود مشکلات دیگری (در ادامه می‌بینیم) است و معمولاً تنها با اشکال زدایی کد منبع توسط برنامه‌نویس اصلاح می‌شود. با این وجود بسیاری از مردم کمبود حافظه را به هر افزایش ناخواسته فضای مصرف شده نسبت می‌دهند، هرچند که این تعریف کاملاً درست نیست.

نتیجه

کمبود حافظه با کاهش دادن مقدار فضای آزاد می‌تواند باعث کاهش کارایی سیستم گردد. سرانجام در بدترین حالت بیشتر فضای موجود اشغال می‌شود که این امر می‌تواند موجب از کار افتادن کل یا بخشی از سیستم و سخت‌افزار، بروز مشکل در نرم‌افزارها یا حتی تأخیر غیرقابل قبولی در سیستمی که منجر به تریشینک می‌شود، بشود.

در استفاده‌های معمولی کمبود حافظه شاید خیلی جدی و قابل احساس نباشد. در سیستم عامل‌های جدید، حافظه مورد استفاده نرم‌افزارها پس از خروج آن‌ها آزاد می‌گردد؛ یعنی کمبود حافظه در برنامه‌هایی که برای مدت کوتاهی اجرا می‌شوند خیلی جدی و قابل احساس نیستند.

بعضی از مواردی که می تواند در کمبود حافظه جدی باشند:

  • زمانی که برنامه‌ای برای مدتی طولانی اجرا می‌گردد و در طول زمان فضای بیشتری مصرف می‌کند، مانند برنامه‌هایی که در پس زمینه سرورها مخصوصاً در سامانه‌های توکار که برای سال‌ها در حالت اجرا باقی می‌مانند.
  • زمانی که حافظه‌ای به‌طور متناوب از سیستم گرفته می‌شود، مثلاً رمانی که فریم‌های یک بازی کامپیوتری یا فیلم را تحلیل می‌کنیم.
  • زمانی که برنامه قادر به درخواست حافظه (مثلاً حافظه به اشتراک گذاشته شده) است ولی حافظه حتی پس از خروج از برنامه آزاد نمی‌شوند.
  • زمانی که کمبود به خاطر خود سیستم عامل به وجود آید.
  • زمانی که کمبود در راه‌انداز سخت‌افزار سیستم به وجود آید.
  • زمانی که حافظه خیلی محدود است مثلاً در سامانه‌های توکار یا دستگاه‌های قابل حمل.
  • زمانی که سیستم عامل پس از بسته شدن برنامه به‌طور خودکار حافظه‌های اخذ شده را آزاد نکند (و فقط پس از راه اندازی مجدد آزاد گردد)، مانند آمیگا او اِس.

مثالی از کمبود حافظه

در مثال بیان شده (در ذیل) که توسط شبه‌کد نوشته شده‌است قصد داریم نشان دهیم که کمبود حافظه چگونه می‌تواند به وجود آید و چگونه موثر باشد(بدون نیاز به هیچ گونه دانش برنامه‌نویسی). این برنامه قسمتی از یک نرم‌افزار ساده‌ای است که به منظور کنترل یک آسانسور نوشته شده‌است. این قسمت از برنامه زمانی که هر شخص داخل آسانسور دکمه مربوط به طبقه همکف فشار دهد، اجرا می‌شود.

زمانی که یک دکمه فشار داده شد
 فضایی برای ذخیره شماره طبقه اخذ کن
 شماره طبقه را در حافظه گرفته شده ذخیره کن
 آیا هم‌اکنون در طبقه همکف هستیم؟
 اگر آره:پس هیچ کاری انجام نده: تمام
 در غیر اینصورت:
    منتظر بایست تا آسانسور آماده کار شود
 به طبقه مطلوب برو
 فضای استفاده شده برای ذخیره شماره طبقه را آزاد کن

در این مثال کمبود حافظه زمانی اتفاق می‌افتد که آسانسور در طبقه درخواست شده باشد. چون در این حالت فضای گرفته شده آزاد نمی‌گردد. هر بار که این عمل تکرار شود حافظه بیشتری گرفته می‌شود.

مواردی شبیه این مثال معمولاً بلافاصله تأثیر نمی‌گذارند. مردم معمولاً کلید مربوط به طبقه‌ای را که در آن هستند فشار نمی‌دهند، در هر حال ممکن است که آسانسور فضای گسسته کافی برای صد یا هزار بار تکرار این پیش آمد را داشته باشد. با این وجود سرانجام فضای حافظه آن پر می‌شود. این کار می‌تواند ماه‌ها یا سال‌ها طول بکشد، پس این مشکل از طریق آزمایش قابل تشخیص نیست.

نتیجه می‌تواند ناخوشایند باشد: زمانی که حافظه آزاد بسیار کم شود آسانسور نمی‌تواند پاسخی به درخواستها بدهد. اگر قسمت‌های دیگر برنامه نیازی به فضا داشته یاشد مثلاً قسمتی که برای باز یا بسته کردن آسانسور نوشته شده‌است از آنجایی که دیگر نرم‌افزار قادر به باز کردن در نمی‌باشد سبب گیر کردن فرد در آسانسور شود.

کمبود حافظه تا زمانی که سیستم دوباره راه اندازی شود باقی می‌ماند. برای مثال اگر منبع آسانسور قطع گردد برنامه متوقف می‌شود. اگر منبع را دوباره وصل کنیم برنامه از اول اجرا می‌شود و فضای حافظه آزاد می‌شود ولی دوباره کم‌کم می‌تواند سبب کمبود حافظه گردد و سرانجام سبب لطمه به اجرای صحیح سیستم گردد.

موضوع برنامه‌نویسی

کمبود حافظه یک خطای معمول در برنامه‌نویسی محسوب می‌شود، مخصوصاً زمانی که از زبانی که در داخل خودش زباله روبی خودکار ندارد استفاده می‌کنیم مانند سی++ یا سی. معمولاً به این علت کمبود حافظه رخ می‌دهد که حافظه پویا به حافظه غیرقابل دسترس تبدیل می‌شود. رواج مشکل کمبود حافظه سبب تولید تعداد زیادی ابزار اشکال‌زدایی که قابلیت تشخیص حافظه غیرقابل دسترس را داشتند شد. قابلیت زباله روبی «محافظه کار» را می‌توان به صورت یک ویژگی داخلی به هر زبانی که آن را ندارد اضافه نمود. کتابخانه‌هایی برای انجام این کار برای سی و سی++ موجود است. زباله روب محافظه کار قابلیت پیدا کردن و آزادسازی بیشتر حافظه‌های غیرقابل دسترس را دارند نه همه.

مدیریت حافظه می‌تواند حافظه‌های غیرقابل دسترس را بازیابی کند، (نمی‌تواند حافظه‌هایی که در در دسترس هستند را آزاد کند) که این امر ذاتاً مفید است. مدیریت حافظه‌های مدرن، تکنونوژی به برنامه نویسان ارائه کردند تا با کمک آن بتوانند براساس تفاوت سطحهای مفید بودن نشانه‌گذاری کنند (تفاوت در سطحهای قابل دسترس بودن). مدیریت حافظه شئ‌هایی را که به‌طور قوی در دسترس هستند را پاک نمی‌کنند. یک شئ را زمانی در دسترس قوی می‌نامیم که توسط یک ارجاع قوی در دسترس باشد یا توسط زنجیره‌ای از ارجاع‌های قوی در دسترس باشد. (زمانی یک ارجاع را ارجاع قوی می‌نامیم که بر خلاف ارجاع ضعیف از زباله روب شدن خودداری کند). بدین منظور توسعه دهندگان مسئول پاک‌سازی ارجاع‌ها پس از استفاده از آن‌ها می‌باشند که این امر معمولاً با مقدار دهی تهی به ارجاع‌هایی که احتیاج ندارند صورت می‌گیرد و اگر لازم باشد با پاک کردن هر گوش دهندهٔ رخدادی که شامل ارجاع قوی به شئ مورد نظر باشد صورت می‌گیرد.

مدیریت حافظه خودکار برای توسعه دهندگان بسیار مناسب است، زیرا آن‌ها احتیاجی به پیاده‌سازی فرایند آزادسازی حافظه ندارند و هیچ گونه نگرانی برای اینکه آیا حافظه به خوبی آزاد شده‌است یا نه ندارد و دیگر نیازی به تمرکز بر اینکه آیا برای حافظه ارجاعی وجود دارد یا نه ندارند. برای برنامه نویسان دانستن اینکه نیازی به ارجاع ندارند راحتنر از این است که بدانند چه زمانی به حافظه هیچ ارجاعی وجود ندارد. با این وجود مدیریت حافظه خودکار باعت کاهش سرعت اجرا می‌شود و نمی‌تواند همه خطاهایی که منجر به کمبود حافظه می‌شود را حذف کند.

آر اِی آی آی

آر اِی آی آی (فراگیری منابع به صورت ارزش دهی اولیه) روشی است که معمولاً در سی++، دی و ایدا برای این مشکل استفاده می‌شود. در این روش، منابع به شئ‌هایی که در بلوک‌ها قرار دارند نسبت داده می‌شوند و به محض اینکه شئ از بلوک خارج شد به‌طور خودکار منابع آزاد می‌شود. بر خلاف زباله روبها، آر اِی آی آی این خوبی را دارد که می‌دانیم که شئ چه زمانی وجود دارد و چه زمانی وجود ندارد. حال این کد سی را با سی++ مقایسه می‌کنیم:

/* C version */
#include <stdlib.h>

void f(int n)
{
  int* array = calloc(n, sizeof(int));
  do_some_work();
  free(array);
}
// C++ version
#include <vector>

void f(int n)
{
  std::vector<int> array (n);
  do_some_work();
}

در نسخه سی، نیاز به آزادسازی به صورت واضح (مستقیم) وجود دارد (آرایه در Heap تخصیص داده می‌شود و تا وقتی که به صورت واضح آزاد نشود در Heap باقی می‌ماند) در نسخه سی++ نیازی به آزادسازی به صورت واضح وجود ندارد و این امر به‌طور خودکار زمانی که از بلوک خارج شد صورت می‌گیرد و نیز شامل زمانی که استثنا رخ می‌دهد می‌شود. این امر بر روی منابع دیگری به جز حافظه قابل اجرا است، از قبیل:

  • دستگیرهٔ فایل‌ها (که زباله روب «علامت و پیچ و خم» نمی‌تواند به خوبی کنترل کند)
  • پنجره‌هایی که باید بسته باشند
  • آیکون‌هایی که باید در نوار اطلاع باید مخفی باشند
  • اتصالات شبکه
  • شئ‌های جی دی آی ویندوز

با این وجود استفاده صحیح از آر اِی آی آی همیشه ساده نیست و مشکلات مربوط به خودش را دارد. برای مثال اگر شخص مراقب نباشد، ممکن است که با برگرداندن داده‌ای که با خروج از بلوک آزاد می‌گردد به صورت ارجاع، باعث به وجود آمدن اشاره گر آویزان شود.

زبان دی از ترکیبی از آر اِی آی آی و زباله روب استفاده می‌کند (استفاده از مخرب خودکار زمانی که شئ به‌طور واضح از بلوک خارج می‌شود و استفاده از زباله روب در غیر این صورت).

شمارنده ارجاع و مرجع‌های چرخه‌ای

طراحی زوباله روب‌های جدید معمولاً بر اساس در دسترس بودن است (اگر هیج ارجاع قابل استفاده‌ای به آن موجود نیست می‌توان آن را جمع‌آوری کرد). دسته‌ای دیگر از زباله روب‌ها بر اساس شمارنده ارجاع است به این صورت که هر شی مسئول نگه‌داری رد مسیر برای دانستن تعداد ارجاع‌ها به آن است. اگر تعداد برابر صفر شود از شئ انتظار می‌رود که خودش را آزاد کند. مشکل این طرح این که نمی‌تواند در مرجع‌های چرخه‌ای (دایره‌ای) به خوبی رفتار کند و به همین دلیل امروزه ما برای پذیرفتن هزینهٔ زیاد سیستم‌های نشانه‌گذاری و زدودن آماده می‌شویم.

در مثال زیر مشکل کمبود حافظه در شمارنده ارجاع به تصویر کشیده شده‌است:

Dim A, B
Set A = CreateObject("Some.Thing")
Set B = CreateObject("Some.Thing")
' At this point, the two objects each have one reference,
Set A.member = B
Set B.member = A
' Now they each have two references.
Set A = Nothing
' You could still get out of it...
Set B = Nothing
' You now have a memory leak.

در این مثال کوچک می‌توان به راحتی مشکل را بر طرف کرد ولی در اکثر مثال‌های واقعی، چرخهٔ اشاره گرها بیش از دو شئ را دربر گرفته و تشخیص آن سخت‌تر است.

یک مثال معروف از این نوع کمبود حافظه، در زبان آژاکس در مرورگرهای وب است.

تأثیرات

اگر یک برنامه کمبود حافظه داشته باشد و حافظهٔ مورد استفاده آن به‌طور یکنواخت افزایش یابد، هیچ نشانه سریعی (سریعاً قابل تشخیص باشد) قابل مشاهده نیست. هر سیستم یک نهایت برای حافظه دارد و با دوباره اجرا کردن برنامه به زودی یا پس از مدتی مشکل تکرار می‌شود.

اکثر استفاده‌کننده‌های سیستم عامل‌های رو میزی از حافظه اصلی (بر روی میکروچیپ‌های رَم) و هم از حافظه ثانویه (مانند دیسک سخت) استفاده می‌کنند. تخصیص حافظه به صورت پویا صورت می‌گیرد (هر عمل به اندازه‌ای که نیاز دارد). صفحه‌های فعال برای دسترسی سریع به حافظهٔ اصلی انتقال می‌یابند و صفحه‌های غیرفعال برای صرفه جویی در حافظه به حافظه ثانویه انتقال می‌یابند. زمانی که یک پروسه شروع به مصرف زیاد حافظه می‌کند معمولاً موفق به اخذ حافظه بیشتر و بیشتر می‌شود و باعث فرستادن برنامه‌های دیگر به حافظهٔ ثانویه می‌گردد (که باعث هزینهٔ زیاد و سرعت کم). حتی اگر برنامه‌ای که سبب کمبود حافظه شده‌است خاتمه داده شود ممکن است مدت زمانی برای برنامه‌های دیگر طول بکشد تا خود را به حافظه اصلی انتقال دهند و کارایی به حالت عادی خود برسد.

زمانی که همهٔ حافظه یک سیستم مصرف شد (زمانی که حافظه مجازی وجود داشته باشد یا فقط حافظه اصلی موجود باشد مانند سیستم‌های جاسازی شده) هر گونه تلاش برای تخصیص حافظه رد می‌شود. این امر سبب می‌شود که برنامه برای خاتمه دادن به خود، شروع به تلاش برای تخصیص حافظه کند یا شروع به تولید قطعه بندی خطا کند. بعضی از برنامه‌ها برای بازیابی از چنین شرایطی طراحی شده‌اند (ممکن است با استفاده از حافظه از قبل رزرو شده). اولین برنامه‌ای با پر شدن حافظه روبه رو می‌شود ممکن است آن برنامه‌ای نباشد که دارای کمبود حافظه است.

بعضی از سیستم عامل‌های چند وظیف ای یک روش مخصوص به خود برای برخورد با پر شدن حافظه دارند مانند حذف تصادفی پروسه‌ها (ممکن است بر روی یک پروسه بی گناه صورت بگیرد) یا حذف پروسه‌ای که از بیشترین حافظه استفاده می‌کند (که احتمالاً یکی از پروسه‌هایی بوده که سبب مشکل بوده). بعضی از سیستم عامل‌ها برای هر پروسه یک محدودیت حافظه دارند تا از به زور گرفتن همه حافظه توسط برنامه جلوگیری کند. بدی این ایده این است که سیستم عامل باید گاهی اوقات دوباره تنظیم گردد تا بتواند به برنامه‌هایی که واقعاً نیاز به فضای زیادی دارند فضای کافی در نسبت دهد (مانند برنامه‌های گرافیکی، ویرایش فیلم و محاسبات علمی).

اگر کمبود حافظه در هسته سیستم عامل رخ دهد سیستم عامل متوقف می‌شود. کامپیوترهایی که از مدیریت حافظه خبره استفاده نمی‌کنند مانند سیستم‌های جاسازی شده ممکن است در اثر کمبود حافظه ماندگار، از کار متوقف شود.

سیستم‌های در دسترس عموم مانند سرویس دهنده‌های وب یا مسیریاب‌ها برای حملات انکار سرویس مستعد هستند در صورتی که حمله‌کننده دنباله از کارهایی که سبب کمبود حافظه می‌شود راکشف کند. مانند دنباله‌هایی که به اکسپلویت موسوم هستند.

افزایش دندانه‌ای

الگوی دندانه‌ای استفاده از حافظه ممکن است نشان دهندهٔ کمبود حافظه باشد اگر پرش‌های کاهشی عمودی هم‌زمان با دوباره بوت کردن یا راه اندازی مجدد نرم‌افزار رخ دهد. البته باید مراقب بود زیرا زباله روب نیز ممکن است سبب تولید چنین الگویی شود.

دیگر مصرف‌کننده‌های حافظه

باید توجه داشته باشیم که افزایش مداوم استفاده از حافظه نمی‌تواند نشان کمبود حافظه باشد؛ مثلاً بعضی از برنامه‌ها به‌طور افزایشی مقدار زیادی از اطلاعات را در حافظه ذخیره می‌کنند (شبیه ذخیره گاه). اگر این ذخیره گاه بتواند به مقدار زیادی افزایش یابد باعث ایجاد مشکل می‌گردد که ممکن است در اثر طراحی یا برنامه‌نویسی نا درست به وجود آمده باشد، ولی این امر کمبود حافظه تلقی نمی‌شود زیرا تمام حافظه استفاده شده قابل دسترسی است. در حالتی دیگر ممکن است برنامه‌نویس به‌طور نامعقولی درخواست حافظهٔ زیادی کند چون که برنامه‌نویس در این تصور بوده که حافظه موجود برای آن کار همیشه در دسترس است. برای مثال پردازش گر فایل‌های گرافیکی شروع به خواندن همهٔ فایل و ذخیره آن در حافظه کند، در صورتی که فضای لازم موجود نباشد برنامه در اجرا دچار مشکل می‌شود.

کمبود حافظه در اثر نوع خاصی از اشکال در برنامه‌نویسی رخ می‌دهد و در صورتی به منبع کد آن دسترسی نداشته باشیم تنها با دیدن نشانه‌های آن می‌توان حدس زد که شاید بر نامه دارای کمبود حافظه است. در صورتی که به درون برنامه هیچ شناختی نداریم بهتر است از عبارت «افزایش مداوم و ثابت حافظه مورد استفاده» استفاده کنیم.

مثالی ساده در زبان سی

در این مثال سی ما عمداً با از دست دادن اشاره گر به حافظه تخصیص یافته سبب کمبود حافظه می‌شویم. به این دلیل که تخصیص حافظه بدون ذخیره آدرس آن به‌طور همیشگی ادامه دارد در نهایت باعت به وجود آمدن توقف (مقدار تهی برمی‌گرداند) در اجرا می‌شود در جایی که حافظه پر شده‌است. به این دلیل که آدرس حافظه تخصیص یافته ذخیره نمی‌شود امکال آزاد کردن حافظه‌های تخصیص یافته قبلی وجود ندارد.

باید به نکته توجه داشت که سیستم عامل تا زمانی که هیچ داده‌ای بر روی حافظه تخصیص یافته نوشته نشده‌است بین تخصیص حافظه واقعی تأخیر می‌اندازد و برنامه زمانی که آدرس‌های مجازی آن از محدوده خارج می‌شود پایان می‌یابد. (برای هر پروسه بین ۲ تا ۴ گیگا بایت محدود است البته برای سیستم عامل‌های ۶۴ بیتی محدودیت کمتری وجود دارد).

#include <stdlib.h>

int main(void)
{
     /* this is an infinite loop calling the malloc function which
      * allocates the memory but without saving the address of the
      * allocated place */
     while (malloc(50)); /* malloc will return NULL sooner or later, due to lack of memory */
     return 0;  /* free the allocated memory by operating system itself after program exits */
}

منابع

  • ویکی‌پدیای انگلیسی. /wiki/%D9%88%DB%8C%DA%A9%DB%8C%E2%80%8C%D9%BE%D8%AF%DB%8C%D8%A7:Memory_leak