در این مقاله سعی شده است چگونگی ساخته شدن Objectها توسط برنامه ها , چگونگی مدیریت طول عمر اشیا در .NET و چگونگی آزاد شدن حافظه های گرفته شده توسط Garbage Collector شرح داده شود.
درک مبانی کار Garbage Collector:
هر برنامه به نحوی از منابع مشخصی استفاده میکند. این منابع میتوانند فایلها، بافرهای حافظه، فضاهای صفحه نمایش، ارتباطات شبکه ای، منابع بانک اطلاعاتی و مانند اینها باشند. در حقیقت در یک محیط شیی گرا هر نوع داده تعریف شده در برنامه معرف یک سری منابع مربوط به آن برنامه هستند. برای استفاده از هر نوع از این داده ها لازم است که برای ارایه آن نوع مقداری حافظه تخصیص داده شود. موارد زیر برای دسترسی به یک منبع مورد نیاز است:
1)تخصیص حافظه برای نوع داده ای که منبع مورد نظر را ارایه میدهد. این تخصیص حافظه با استفاده از دستور newobj در زبان IL صورت میگیرد که این دستور از ترجمه دستور new در زبانهایی مثل C# و Visual Basic و دیگر زبانهای برنامه نویسی ایجاد میشود.
2)مقداردهی اولیه حافظه برای تنظیم حالت آغازین(Initial state) منابع و قابل استفاده کردن آن. توابع Constructor در این نوع داده ها مسئول این تنظیمات برای ایجاد این حالت آغازین هستند.
3)استفاده از منابع با دسترسی به اعضای موجود در نوع داده.
4)از بین بردن حالت کلی منابع برای پاک کردن آن.
5)آزادسازی حافظه. Garbage Collector مسئول مطلق این مرحله به شمار می رود.
این نمونه به ظاهر ساده یکی از ریشه های اصلی خطاهای ایجاد شده در برنامه نویسی به شمار میرود. مواقع زیادی پیش می آید که برنامه نویس آزادسازی یک حافظه را وقتی دیگر مورد نیاز نیست فراموش می کند. مواقع زیادی پیش می آید که برنامه نویس از یک حافظه که قبلا آزاد شده استفاده کند.
این دو باگ برنامه ها از اکثر آنها بدتراند زیرا معمولا برنامه نویس نمیتواند ترتیب یا زمان به وجود آمدن این خطاها را پیش بینی کند. برای دیگر باگها شما میتوانید با مشاهده رفتار اشتباه یک برنامه آن را به سادگی تصحیح کنید. اما این دو باگ موجب نشت منابع (Resource Leak) (مصرف بیجای حافظه) و از بین رفتن پایداری اشیا میشوند که کارایی برنامه را در زمانهای مختلف تغییر میدهد. برای کمک به یک برنامه نویس برای تشخیص این نوع خطاها ابزارهای ویژه ای مانند Windows Task Manager و System Monitor ActiveX Control و NuMega Bounds Checker و ... طراحی شده اند.
یک مدیریت منبع مناسب بسیار مشکل و خسته کننده است. این مورد تمرکز برنامه نویس را بر روی مطلب اصلی از بین میبرد. به همین دلیل نیاز به یک مکانیسم که مدیریت حافظه را به بهترین نحو انجام دهد در این زمینه به وضوح احساس میشد. در پلتفرم .NET این امر توسط Garbage Collector انجام میشود.
Garbage Collection کاملا برنامه نویس را از کنترل استفاده از حافظه و بررسی زمان آزادسازی آن راحت میکند. اگرچه Garbage Collector درمورد منابع ارائه شده توسط نوع داده در حافظه هیچ چیز نمیداند، یعنی Garbage Collector نمیداند چه طور میتواند مرحله 4 از موارد بالا را انجام دهد: از بین بردن حالت کلی منابع برای پاک کردن آن. برنامه نویس باید کدهای مربوط به این قسمت را انجام دهد چون او میداند باید چه گونه حافظه را به درستی و کاملا آزاد کند. البته Garbage Collector میتواند در این زمینه نیز قسمتهایی از کار را برای برنامه نویس انجام دهد.
البته، بیشتر نوع داده ها، مانند Int32، Point ، Rectangle ، String ،ArrayList و SerializationInfo از منابعی استفاده می کنند که احتیاجی به نوع ویژه ای از آزادسازی حافظه ندارند. برای مثال منابع یک شئی از نوع Point به راحتی و با نابود کردن فیلدهای X و Y در حافظه شیی آزاد میشود.
از طرف دیگر، یک نوع داده که منابع مدیریت نشده ای را ارائه میدهد، مانند یک فایل، یک ارتباط بانک اظلاعاتی، یک سوکت، یک Bitmap، یک آیکون و مانند اینها همیشه به اجرای مقداری کد ویژه برای آزاد کردن حافظه گرفته شده نیاز دارند.
CLR نیاز دارد که حافظه تمام منابع از یک heap مخصوص که managed heap نامیده میشود تخصیص داده شود. این heap شبیه heap زمان اجرای C است و فقط از یک لحاظ متفاوت است و آن این است که در این heap شما هیچ وقت حافظه تخصیص داده شده را آزاد نمیکنید. در حقیقت اشیا موجود در این heap وقتی دیگر نیازی به آنها نباشد آزاد میشوند. این مورد این سوال را ایجاد میکند که چگونه managed heap متوجه میشود که دیگر نیازی به یک شیی خاص نیست؟
چندین الگوریتم از Garbage Collector در حال حاضر در مرحله آزمایش هستند و هر کدام از این الگوریتمها برای یک محیط خاص و نیز برای کسب بهترین راندمان بهینه سازی شده اند. در این مقاله روی الگوریتم Garbage Collector استفاده شده در Microsoft .NET Framework CLR متمرکز شده است.
زمانی که یک پروسه مقداردهی اولیه(Initialize) میشود، CLR یک قسمت پیوسته از آدرس حافظه را برای آن اختصاص میدهد این آدرس فضای حافظه managed heap نامیده میشود. این heap همچنین یک اشاره گر مخصوص هم دارد که ما از این به بعد آن را NextObjPtr می نامیم. این اشاره گر مکان قرار گیری شیی بعدی را در heap مشخص میکند. در ابتدا این اشاره گر به آدرس ابتدای فضای گرفته شده برای managed heap اشاره میکند.
دستور newobj در زبان IL باعث ایجاد یک شیی جدید میشود. بیشتر زبانها از جمله C# و Visual Basic برای درج این دستور در کد IL عملگر new را در برنامه ارائه میدهند. این دستور IL باعث میشود که CLR مراحل زیر را انجام دهد:
1)محاسبه تعداد بایتهای مورد نیاز برای این نوع داده
2) اضافه کردن بایتهای مورد نیاز برای overhead شیی. هر شیی دو فیلد overhead دارد: یک اشاره گر به جدول تابع و یک SyncBlockIndex. در سیستمهای 32بیتی، هر کدام از این فیلدها 32 بیت هستند، که 8 بایت را به هر شیی اضافه می کند. در سیستم های 64 بیتی، هر کدام از این فیلدها 64 بیت است که 16 بایت را برای هر شیی اضافه می کند.
3)سپس CLR چک میکند که حافظه مورد نیاز برای شیی جدید در managed heap موجود باشد. اگر فضای کافی موجود باشد این شیی در آدرسی که NextObjPtr به آن اشاره میکند ایجاد میشود. تابع constructor شیی مذکور فراخوانی میشود (اشاره گر NextObjPtr به عنوان پارامتر this به constructor فرستاده میشود) و دستور newobj آدرس شیی ایجاد شده را برمیگرداند. درست قبل از اینکه آدرس برگردانده شود، NextObjPtr به بعد از شیی ایجاد شده پیشروی میکند و مثل قبل آدرسی که باید شیی بعدی در آن قرار گیرد را در خود نگه میدارد.
شکل زیر یک managed heap را که سه شیی Aو B و C را درخود نگه میدارد را نشان میدهد. اگر یک شیی جدید ایجاد شود این شیی دقیقا در جایی که NextObjPtr به آن اشاره میکند قرار میگیرد (درست بعد از شیی C).