آخرین خبرها
خانه | مقالات | شبکه | ورود به دالان‌های دات‌نت

ورود به دالان‌های دات‌نت

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

Leak memory چیست؟
قبل از این‌که چگونگی نظارت دات‌نت بر نحوه مدیریت حافظه را بررسی کنیم، ابتدا بهتر است درباره مفهومی آشنا در دنیای برنامه‌نویسی بیش‌تر بدانیم. Leak Memory اصطلاحی است که کمتر طراح حرفه‌ای نرم‌افزاری ممکن است آن‌ را نشنیده باشد. اما Leak Memory یا همان نشتی حافظه به چه معنا است؟ در کوتاه‌ترین و ساده‌ترین تعریف، نشتی حافظه زمانی اتفاق می‌افتد که برنامه‌ای از حافظه اصلی سیستم استفاده کرده، اما حافظه مصرفی را به سیستم بر نگردانده باشد. به عبارت دیگر، برنامه‌نویس در نحوه تخصیص و مدیریت حافظه به وظیفه خود به درستی عمل نکرده باشد.
یک آرایه کاراکتری که با استفاده از دستور new ساخته شده، اما برای آزاد‌سازی آن ([]delete) اقدامی صورت نگرفته است، نمونه‌ای بارز از این دست به شمار می‌رود. حال اگر این فرآیند در یک متد تکرار شونده در یک برنامه، پیوسته فراخوانی شود، برنامه به‌طور مرتب حافظه قابل توجهی را استفاده می‌کند، اما آن‌را به سیستم باز نمی‌گرداند. نتیجه این فرآیند کاهش حافظه آزاد سیستم خواهد بود، به‌طوری‌که نه فقط برنامه‌های دیگر، سرویس‌های اصلی و حیاتی سیستم نیز دچار کمبود حافظه می‌شوند.

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

 چگونه می‌توان مشکل نشتی حافظه را پیدا کرد؟
نشتی حافظه را شاید بتوان به‌منزله یکی از بدترین نوع خطاهای یک برنامه در نظر گرفت، زیرا هیچ‌گونه نشانه‌ای از خطا، چه به لحاظ ترکیب نحوی و چه به لحاظ زمان اجرا، تولید نخواهد شد، به دلیل عدم تولید خطا، برنامه‌نویس در شرایط عادی از وجود آن مطلع نمی‌شود و منابع سیستم به سرعت بدون استفاده می‌شوند.
شاید برنامه‌نویسان ++C/C  را بتوان در زمره کدنویسی‌هایی بر‌شمرد که بیشترین مشکل را با این موضوع دارند. زیرا این زبان‌های برنامه‌نویسی در مدیریت حافظه دست برنامه‌نویسان را باز گذاشته‌اند و برنامه‌نویس نه تنها دسترسی مستقیم به حافظه دارد، انواع گوناگونی از عملیات را نیز می‌تواند انجام دهد که همان جمله معروف را به یاد می‌آورد: «قدرت زیاد مسئولیت زیاد به‌همراه  می‌آورد.»
اما نکته‌ای که در اینجا باید به آن اشاره کنیم، این است که افزایش غیرمنطقی در میزان مصرف حافظه سیستم به منزله نشانه‌ای از نشتی حافظه شناخته می‌شود. برای نمونه، اگر حافظه را برای یک برنامه خاص از زمان اجرای آن برای نخستین‌بار بررسی کرده ودوباره آن‌را با استفاده از روتین‌های پیچیده مورد ارزیابی قرار دهیم و memory page افزایش پیدا کرده باشد، این حالت نمونه‌ای از نشتی حافظه خواهد بود.

اما برای شناسایی نشتی حافظه سه فرآیند باید طی شود:

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

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

3- ترمیم نشتی حافظه، بعد از این‌که دو مرحله یاد شده را کامل سپری کردید، این مرحله بخش ساده کار است. ترمیم نشتی حافظه به معنای نوشتن کدهای اضافه‌تری  است که سبب آزاد‌سازی حافظه در مسیرهایی خواهد بود که مشکوک به این فرآیند می‌شوند.

اما یکی از مفیدترین و در عین حال ساده‌ترین ابزارهایی که با آن می‌توانید پردازه‌های مشکوک (مشکوک به لحاظ میزان حافظه مصرفی به خصوص برای پردازه‌هایی که شناخته شده نیستند یا پردازه‌هایی که به‌عنوان بدافزار روی یک سیستم ممکن است به‌صورت مقیم در حافظه (TSR قرار داشته باشند و همچنین برای وظایف مدیریتی.) را کشف کنید، ابزار Task Manager و همچنین ابزار Performance Monitor در خود سیستم‌عامل ویندوز است.
این ابزارها برای کمک به طراحان نرم‌افزار و مدیران شبکه‌ برای نظارت و کنترل روی ماجول‌های اصلی ویندوز و مؤلفه‌های جانبی نصب شده روی سیستم ساخته شده است که ضمن مانیتور و بررسی‌کردن فعالیت‌های مختلف در حال اجرا روی سیستم‌عامل بتوانند در‌مواردی این‌چنینی نیز مورد استفاده قرار گیرند. شکل‌1 ستون Memory م(Peak Memory) را نشان می‌دهد که نشان‌دهنده میزان حافظه مصرفی به کمک پردازش‌ها است.

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

 محیط زمان اجرا (Common Language Runtime) چیست؟
دات‌نت از مجموعه‌ای از مؤلفه‌های اصلی ساخته شده است که هر کدام وظایف فراوانی همچون کنترل هماهنگی نوع‌های مورد استفاده، پیاده‌سازی فرآیندهای ساخت برنامه‌ها و… را بر عهده دارند. یکی از عناصر اصلی دات‌نت را CLR شکل می‌دهد که به آن محیط زمان اجرا می‌گویند، این عنصر ضمن اجرای کدها، وظیفه فراهم کردن سرویس‌هایی را نیز بر عهده دارد که چرخه طراحی نرم‌افزارها را ساده‌تر می‌کند.CLR را می‌توان همچون یک محیط کنترل و مدیریت شده‌ دانست که همه اشیا درون آن از اصول مشخص و خاصی تبعیت می‌کنند و همین موضوع باعث ساخت‌مندی این محیط و برنامه‌های تولید شده بر‌مبنای آن شده  است.
اما برای این‌که CLR توانایی فراهم‌کردن سرویس‌ها را برای کدهای مدیریت شده داشته باشد، کامپایلرهای زبان باید فراداده یا متادیتاهایی را برای CLR فراهم کنند که وظیفه آن‌ها توصیف نوع‌ها‌، اعضا و ارجاعات به‌کار رفته در کدهای نوشته شده در زبان برنامه‌نویسی است.
این فراداده درون کدهای خود برنامه ذخیره می‌شوند و هر زمان که برنامه اجرایی و portable executable م(PE) بارگذاری می‌شود، فراخوانی می‌شوند. در مجموع، ‌کدهایی را که شما، ‌در جایگاه یک برنامه‌نویس،‌ طراحی کرده‌اید، کدهای مدیریت شده (managed code) نامیده می‌شوند،‌ که این کدها را کامپایلرهایی زبانی‌ می‌سازند که برمبنای CLR طراحی شده‌اند.
cross-language integration
cross-language exception handling
enhanced security
versioning and deployment support
a simplified model for component interaction
debugging and profiling services

فرآیند ساخت و اجزای ماجول‌هایی اجرایی دات‌نت هنگامی که کد‌نویسی یک برنامه به پایان رسید، زمان ساخت فایل اجرایی رسیده است، فرآیندی که برای کامپایل یک برنامه اجرا می‌شود، درنهایت یک ماجول مدیریت شده (managed module) را تولید می‌کند که یک فایل اجرایی است. CLR کد IL را دریافت کرده و آن‌را به دستورالعمل زبان ماشین ترجمه و سپس اجرا می‌کند. این فایل می‌تواند به‌صورت 64 یا 32 بیتی باشد که به‌صورت PE32 یا +PE32 تعریف می‌شود، کدی که به‌صورت کد مدیریت شده یا managed code ساخته می‌شود، برای اجرا نیاز به CLR دارد. یکی از مزیت‌های استفاده از CLR پشتیبانی آن از مفهوم DEP (سرنام Data Execution Prevention) است، در نتیجه اسمبلی‌های مدیریت شده می‌توانند از این قابلیت استفاده کنند. دیاگرام زیر مراحل پردازش کد نوشته شده در زبان #C  را نشان می‌دهد که به تولید یک  Managed Module منجر شده و در‌نهایت با استفاده از کامپایلر JIT به native code تبدیل می‌شود.

اجزا تشکیل دهنده یک Manage Module
زمانی که یک ماجول یا همان managed PE ساخته می‌شود (مهم نیست از چه زبان تحت .Net استفاده می‌کنید) از چهار قسمت تشکیل می‌شود. این چهار قسمت عبارت‌اند از:
1. PE32 یا +PE32
2. CLR Header
3. Metadata
4. IL Code
اگر بخواهیم برای چهار قسمت یاد شده یک دیاگرام ترسیم کنیم دیاگرامی همانند شکل زیر خواهیم داشت.

 +(PE(32
(Portable Executable) فایل‌هایی اجرایی را شامل می‌شود که به‌صورت باینری هستند. فایل‌های exe ،dll نمونه‌ای از این موارد به شمار می‌روند. یک فایل exe دارای یک ساختار داده‌ای است که اطلاعات مورد نیاز برای بارکننده ویندوز را در خود دارد. بخش header یک فایل اجرایی، می‌تواند به دو صورت باشد. در حالت نخست PE32 نشان‌دهنده فایلی است که می‌تواند روی یک سیستم‌عامل 32 یا 64 بیتی ویندوز اجرا شود، در حالی‌که یک فایل اجرایی +PE32 فقط روی سیستم‌عامل‌های 64 بیتی اجراپذیر خواهد بود. اما این header اطلاعات دیگری را نیز نشان دهد. این header می‌تواند تعیین کند که فایل شما یک فایل dll، یک فایل 1GUI یا یک فایل CUI (این اصطلاحات بیانگر رابط کاربری هستند) است، همچنین یک timestamp زمان ساخته شدن فایل را نشان می‌دهد. زمانی‌که فایلی را در ویندوز اجرا می‌کنید، ویندوز آزمایش‌های مختلفی را روی آن انجام می‌دهد، یکی از این آزمایش‌ها بررسی header از فایل اجرایی است. با بررسی روی سرآیند فایل امکان تخصیص مناسب فضای آدرسی‌دهی امکان پذیر می‌شود که این فضا به صورت 32 بیتی اختصاص داده شود یا به صورت 64 بیتی، نسخه‌های 64 بیتی ویندز از فناوری ویژه‌ای به نام 2Wow64 (سرنام Windows on Windows64) استفاده می کنند. زمانی‌که نوع فایل به درستی تشخیص داده شود پردازه مربوط به فایل ایجاد می شود.

CLR Header
چنان‌که در قسمت قبل گفته شد، یک فایل اجرایی ساختاری دارد که از چند section (یک section یک بلوک از داده‌ها است) تشکیل شده است. CLR یکی از بلوک‌های کوچک اطلاعاتی درون یک فایل اجرایی ساخته شده با .Net است و شامل اطلاعاتی است که یک ماجول مدیریت شده را می‌سازد.
CLR Header موارد مختلفی از اطلاعات را توصیف می‌کند. مواردی همچون نسخه CLR (به عنوان مثال 2.05)، MethodDef نقطه‌ای که برنامه از آن شروع می‌شود (متد Main)، اطلاعات مربوط به نسخه  minor ،major و همچنین نسخه metadata  (همانند v2. 0. 50727.)هستند.  بعد از CLR header قسمت metadata می‌آید که خود آن به استریم‌هایی تقسیم می‌شود.

Metadata
یک metadata توصیف‌کننده متدها، رابط‌ها و کلاس‌هایی است که در یک ماجول قرار دارد. هر ماجول مدیریت شده‌ای جدول‌های metadata را شامل می‌شود. Metadata از جدول‌های داده‌ای مختلفی تشکیل شده است که توصیف کننده موجودیت‌های استفاده شده در یک ماجول است. سه دسته از این جدول‌ها عبارت‌اند از:

– definition tables: شامل نوع‌ها و عضوهای تعریفی است که در برنامه‌تان وجود دارد و از آن‌ها استفاده می‌کنید. نمونه‌ای از جدول‌های این گروه عبارت‌اند از: ModuleDef ،ParamDef و…

– reference tables: جدول‌های این گروه عضوها و نوع‌هایی را شامل می‌شود که درون برنامه به آن‌ها ارجاع داده شده است. به عبارت دیگر، عضوها و نوع‌هایی که از آن‌ها استفاده می‌شود. نمونه‌ای از این جدول‌ها AssemblyRef ،TypeRef و… هستند. manifest tables یک manifest شامل داده‌های یک اسمبلی است. جدول‌های manifest عبارت‌اند از
ManifestResourceDef ،ExportedTypesDef ،AssemblyDef ،FileDef.
شکل 2 و3 نمونه‌ای از این جدول‌ها را نشان می‌دهند.

– IL code: کدIL یا Intermediate Language کد تولید شده توسط کامپایلر برای سورس دستوراتی است که آن‌ها را در برنامه خود نوشته‌اید. این کد باینری در زمان اجرا توسط CLR تبدیل به دستورالعمل‌های پردازشگر مرکزی می‌شود.

JIT Compiler 
کامپایلر JIT کد IL یا همان MSIL را تبدیل به دستورالعمل‌ها یا کدهای اجرایی پردازشگر مرکزی می‌کند که در اصطلاح به آن native code می‌گویند.

 استخراج اطلاعات یک فایل اجرایی
هدف از استخراج اطلاعات یک فایل اجرایی پی بردن به پلتفرم و نوع فایل اجرایی است که می‌تواند روی آن اجرا شود. برای پی بردن به این اطلاعات می‌توانید به صورت زیر عمل کنید.
1- ابتدا command prompt مربوط به Visual studio را از پوشه این ابزار باز کنید (شکل4).

2- فراخوانی دستور corflags که یکی از ابزارهای ارائه شده با NET. است؛ با استفاده از آن می‌توانید قسمت corflags در یک فایل اجرایی ساخته شده با CLR را ببینید یا آن‌را پیکربندی کنید. در این مرحله فرمان corflags را به همراه مسیر و نام فایل اجرایی مورد نظر را وارد کنید. (شکل5 خروجی فرمان فوق را نشان می‌دهد.)

corflags c:\hld. exe

در شکل 5 دو مقدار PE و 32BIT وجود دارند که نشان دهنده نوع یک اسمبلی هستند. مقادیر نشان داده شده در این تصویر به تنظیماتی بر‌می‌گردد که در زمان ساخت فایل آن‌را برای برنامه کاربردی پیکربندی کرده‌اید. شکل 5 یک فایل 32 بیتی را نشان می‌دهد که پلتفرم x86 برای آن تعیین شده است. اگر PE برابر با +PE32 باشد و 32BIT برابر با 0 این موضوع نشان دهنده یک فایل 64 بیتی است. شکل6 نحوه پیکربندی یک برنامه کاربردی برای یک پلتفرم خاص را نشان می‌دهد.

اگر در شکل6 دقت کرده باشید، platform target روی گزینه x64 تنظیم شده است که به معنی اجرای برنامه فقط روی یک سیستم 64 بیتی خواهد بود. در صورتی‌که از فرمان corflags برای بررسی فایلی استفاده کنید که با استفاده از این پیکربندی ساخته شده است خروجی شما همانند شکل7 خواهد بود.

همچنین برای نکته پایانی این قسمت، اگر اگر برای ساخت یک برنامه تصمیم گرفته‌اید، ‌اما اطمینان ندارید که روی یک سیستم 32 بیتی یا 64 بیتی اجرا شود، می‌توانید از یک محاوره ساده در برنامه‌تان استفاده کنید. خاصیت Is64BitOperatingSystem به شما در این امر یاری می‌رساند. در صورتی‌که برنامه‌ای روی یک سیستم عامل 64 بیتی اجرا شود، مقدار برگردانده شده توسط این خاصیت برابر با true خواهد بود.

Console. WriteLine(Environment. Is64BitOperatingSystem);

تخصیص حافظه در برنامه‌های دات‌نت چگونه است؟
مدیریت خودکار حافظه از جمله سرویس‌هایی است که آن را CLR‌، در مدت زمان اجرای یک ماجول مدیریت شده تولید می‌کند . این وظیفه مدیریتی بر‌عهده garbage collection است که اختصاص و آزاد‌سازی حافظه را برای یک برنامه انجام دهد. اما خبر خوش برای طراحان، عدم نیاز به نوشتن کدهای مربوط به مدیریت حافظه در برنامه کاربردیشان است. مدیریت خودکار حافظه توانایی برطرف کردن برخی از مشکلات رایج را بر عهده دارد. مواردی همچون آزاد‌سازی شیئی که باعث بروز نشتی حافظه می‌شود یا کوشش در دسترسی به حافظه برای شیئی که قبلاً آزاد شده است، نمونه‌ای از این موارد به شمار می‌رود. زمانی‌که یک پردازه جدید را مقدار‌دهی اولیه می‌کنید، runtime یک بخش پیوسته از فضای آدرس‌دهی را برای یک پردازه رزرو می‌کند. این فضای آدرس‌دهی رزور شده، به‌نام managed heap نامیده می‌شود. managed heap یک اشاره‌گر به این فضا دارد، مکانی که شئ بعدی در heap بعد از آن مقدار‌دهی و تخصیص‌‌دهی خواهد شد. این اشاره‌گر به آدرس پایه و اصلی managed heap تنظیم می‌شود، به‌طوری‌که همه نوع‌های ارجاعی روی manage heap قرار گیرند. زمانی‌که برنامه‌ای نخستین نوع ارجاعی خود را می‌سازد، حافظه برای این نوع در آدرس پایه base در managed heap ساخته می‌شود. در زمان بعدی که شئ دیگری توسط برنامه ساخته می‌شود،  garbage collector (که در ادامه با آن آشنا خواهیم شد) عمل تخصیص فضای موردنظر را درست بعد از نخستین شئ ساخته شده انجام می‌دهد. به همین ترتیب، مادامی که فضای آدرس‌دهی وجود داشته باشد،
garbage collector به اختصاص فضای موردنیاز اشیای جدید به همین منوال عمل می‌کند. شکل8 نحوه تخصیص اشیا روی Heap را نشان می‌دهد.

این فرآیند تخصیص حافظه در روش managed heap سریع‌تر از تخصیص غیرمدیریت شده unmanaged heap عمل می‌کند، به دلیل این‌که runtime عمل تخصیص حافظه را برای یک شئ، با اضافه کردن یک مقدار به یک اشاره‌گر انجام می‌دهد. به‌طوری‌که این فرآیند در مقایسه با پشته stack سرعت بالاتری دارد. همچنین به دلیل این‌که اشیا به‌طور پیوسته و متصل در کنار یکدیگر، در حافظه قرار می‌گیرند، در نتیجه یک برنامه کاربردی به سرعت می‌تواند به اشیا خود دسترسی پیدا کند. شکل9 یک مدل ساده شده از Managed Heap را نشان می‌دهد.

زمانی‌که از یک محیط مدیریت شده همانند CLR استفاده می‌کنید، CLR کنترل امور را به‌دست دارد و این اطمینان را به شما می‌دهد که مشکلات حافظه کمتر شوند. کاری که CLR انجام می‌دهد، اختصاص حافظه به برنامه و شئ‌هایی است که از آن‌ها در برنامه استفاده می‌کنید. CLR مجموعه‌ای از سرویس‌های اصلی است که هر کدام وظایفی دارند، اما از میان سرویس‌های اصلی  CLR ،memory management و Garbage Collection دو سرویس مهم به شمار می‌روند. عملکرد CLR در خصوص اختصاص حافظه به اشیا به این‌گونه است که خودش همه منابع روی حافظه Heap را به برنامه‌ها اختصاص داده و زمانی‌که برنامه از شئ استفاده نمی‌کند، آن‌را آزاد می‌کند. garbage collection از الگوریتم‌های متعددی برای آزاد‌سازی منابع استفاده می‌کنند که هر کدام بسته به محیط خاصی استفاده می‌شوند. اما این آزاد‌سازی در شرایطی صورت می‌گیرد. این شرایط عبات‌اند از:
1- زمانی‌که احتمال بروز یک threshold وجود دارد.
2- زمانی‌که کاربر garbage collection را به‌طور دستی فراخوانی می‌کند.
3- زمانی‌که حافظه سیستم به لحاظ کمیت به حالت بحرانی می‌رسد.

 Garbage Collection چیست؟
حال که به‌طور مختصر با اجزای داخلی دات‌نت و مفاهیم مرتبط با مدیریت حافظه و مسائل پیرامونی آن آشنا شدید، زمان آن رسیده است تا با یکی از اجزای دات‌نت که در همان روزهای اولیه ظهور خود به شدت مورد توجه قرار گرفت، آشنا شویم. Grabage Collection چیست؟ اگر بخواهیم تعریف ساده‌‌ای از GC داشته باشیم، می‌توان آن‌را به این صورت تعریف کرد: GC مسئول جمع‌آوری اشیایی است که یک برنامه از آن استفاده می‌کنند. اما به‌صورت تخصصی‌تر GC و عملکرد آن به این صورت است که GC حافظه Heap را در یک فاصله زمانی مشخص (در هر سیکل) برای شئ‌های موجود درون آن، بررسی می‌کند و اگر شئ برای مدت طولانی در یک برنامه استفاده نشود، عمل آزاد‌سازی حافظه برای آن شئ را انجام می‌دهد. این فرآیند می‌تواند به صورت خودکار یا دستی با فراخوانی متد GC. Collect اجرا شود. عمل آزاد‌سازی، زمانی اجرا و کامل می‌شود که GC شئ‌ها را به Nothing تنظیم کند.
اما پرسش مهم این است که چگونه GC توانایی پیمایش حافظه را برای اشیای مذکور داشته و چگونه از این موضوع مطلع می‌شود؟ برای این‌که بتوانیم به جواب دقیقی در این‌باره دست پیدا کنیم، لازم است تا یک لایه به سمت عمق برنامه‌های ساخته شده در دات‌نت پیش رویم تا ببینیم این برنامه‌ها از چه اجزای داخلی بهره برده‌اند. زمانی‌که برنامه‌ای بر‌مبنای CLR ساخته می‌شود، چند ریشه Root خواهد داشت یا به عبارت دیگر درون آن قرار می‌گیرد. این ریشه‌ها وظیفه مشخص‌کردن مکان ذخیره‌سازی اشیایی را شامل می‌شوند که روی managed heap قرار می‌گیرند. این ریشه‌ها شبیه اشاره‌گرهایی به اشیا هستند؛ اشیایی که روی حافظه heap قرار می‌گیرند. یک ریشه، یک مکان ذخیره‌کننده، یک اشاره‌گر درون حافظه است که به یک نوع ارجاعی اشاره دارد. این اشاره‌گرها دو حالت دارند: در حالت نخست به اشیایی اشاره می‌کنند که در حافظه heap قرار دارند و در حالت دوم به null اشاره می‌کنند. برای نمونه، همه اشاره‌گرهای ایستا و سراسری برنامه که به منزله قسمتی از برنامه شناخته می‌شوند در حکم بخشی از ریشه یک برنامه به شمار می‌روند. همچنین هر پارامتر یا متغیر محلی به‌عنوان اشاره‌گرهایی که روی پشته یک رشته پردازشی قرار دارند نیز به‌عنوان ریشه‌های یک برنامه شناخته می‌شوند. CLR و کامپایلر JIT دارای لیستی از ریشه‌های active هستند، لیستی که در قالب یک جدول داخلی از مجموعه‌ای از آدرس‌های حافظه تشکیل شده است که می‌تواند توسط الگوریتم garbage collector مورد استفاده قرار گیرد. ریشه‌ها دو نوع دارند: Strong reference و walk reference.

GC چگونه فرآیند آزاد‌سازی را انجام می‌دهد؟
زمانی که GC شروع به کار می‌کند، ابتدا همه اشیا را به‌صورت Garbage علامت‌گذاری می‌کند، به این معنی که هیچ کدام از اشیا موجود روی حافظه heap قابل استفاده نیستند. بعد از این مرحله شروع به پیمایش لیست ریشه‌ها کرده و در این مدت برای اشیای درون این لیست یک گراف را ترسیم می‌کند. بعد از ساخت گراف همه اشیای درون heap را این‌بار به‌صورت قابل استفاده علامت‌گذاری می‌کند، زمانی که گراف ساخته می‌شود Garbage Collection یک تصویر کامل از آن‌ها را در‌اختیار دارد. فایده استفاده از گراف در این است که GC بعد از بررسی کردن ریشه‌ها می‌داند که شئ‌های درون این گراف به چیزی اشاره می‌کنند. این اشاره می‌تواند هم به‌صورت مستقیم و هم غیر‌مستقیم باشد. غیرمستقیم به معنی شئ که به شئ دیگری اشاره می‌کند. حال اگر شیئی درون این گراف وجود نداشته باشد این تصویر را برای GC تداعی می‌کند که شئ مذکور برای برنامه غیرقابل دسترس و در نتیجه غیرقابل استفاده است و باید به‌عنوان یک garbage علامت‌گذاری و حذف شود.
در این مرحله فرآیند حذف این‌گونه اشیا از حافظه صورت گرفته و فرآیند فشرده کردن حافظه آغاز می‌شود. اشیایی را که به‌صورت garbage علامت‌گذاری می‌شوند، می‌توان به‌صورت یک حفره در حافظه در نظر گرفت، زیرا برای برنامه استفاده‌ای نداشته و فقط باعث از دست رفتن فضا و یکپارچگی حافظه شده و در نتیجه پیوستگی را از بین می‌برند. بعد از حذف این‌گونه اشیا لازم است که اشیا موجود، پشت سر هم قرار گیرند. GC همچنین وظیفه مشخص کردن مکان اشیای جاری را نیز برعهده داشته و همچنین باید آن‌ها را به مکان دیگری انتقال دهد. بعد از کامل شدن این فرآیند، ریشه‌های برنامه باید به نقطه جدید اشاره کنند. در اینجا اگر اشیایی وجود دارند که شامل یک اشاره‌گر به شئ دیگری باشند، GC باید این موارد را نیز بررسی کرده و آن‌ها را اصلاح کند. نتیجه این عملیات بالا بردن کارایی برنامه خواهد بود. اما برای این‌که این عملیات به‌درستی انجام شده و همچنین کارایی برنامه‌ها را تحت‌تأثیر منفی قرار ندهد، برای حل این مشکل GC از مفهومی به‌نام generations استفاده می‌کند، GC همچنین نیاز به سپری کردن زمان برای انجام فعالیت‌هایش دارد (حداقل یک سیکل کاری). این امر به این دلیل است که managed heap در مقایسه با GC از سرعت بالاتری برخوردار است. در نتیجه این فرآیندها باعث بالا رفتن کارایی و بهینه‌سازی GC می‌شوند.  شکل10 این فرآیند را نشان می‌دهد.

نکته‌ای که درباره Garbage collection باید به آن اشاره کنیم، در شرایطی که GC نتواند حافظه خالی را در زمان ساخت یک شئ جدید پیدا کند، در این‌گونه زمان‌ها پیغام خطا یا همان استثنای  Out Of Memory Exception را تولید می‌کند. البته ردیابی این خطا در داخل برنامه کاربردی به آسانی با استفاده از بلوک‌های try/catch می‌تواند انجام گیرد. حالت دیگری که سبب بروز این استثنا می‌شود زمانی است که CLR حافظه کافی برای انجام فعالیت‌های داخلی خود نداشته که در چنین زمان‌هایی معمولاً، برنامه کاربردی به‌طور ناگهانی خاتمه پیدا می‌کند.

جمع‌بندی
در این مقاله به‌صورت کوتاه عواملی را معرفی کردیم که باعث کاهش کارایی و بهره‌وری برنامه‌ها می‌شوند و مشاهده کردیم که عدم توجه به مسائلی همانند بازگرداندن منابع به‌کارگرفته شده در یک برنامه به سیستم، چگونه بر روند کارایی یک سیستم تأثیر نامطلوب می‌گذارند.
پس از آن، به CLR نگاهی انداخته و با اجزایی آشنا شدیم که در درون یک فایل اجرایی ساخته شده توسط دات‌نت قرار می‌گیرند. در‌نهایت، به سراغ مفهوم تخصیص حافظه در برنامه‌های دات‌نت رفته و متوجه شدیم که چگونه دات‌نت با استفاده از تکنیک Garbage collection عمل آزادسازی یا به اصطلاح جاروب کردن حافظه را انجام می‌دهد. البته، همان‌گونه که عموم طراحان حرفه‌ای دات‌نت اطلاع دارند، مبحث GC به این چند صفحه محدود نشده و بسیار گسترده‌تر از این  مقاله است.
ما در این مقاله به مفاهیم دیگری که پیرامون GC قرار دارند، همانند Generations  ،Weak Reference و… نپرداختیم و خواندن این مفاهیم را به عهده خوانندگان می‌گذاریم.

پا‌نویس
1 –  اصطلاحات فوق نوع برنامه کاربردی را مشخص می‌کنند. در حالت CUI (سرنام Character User Interface)  از صفحه کلید برای ارتباط با برنامه استفاده می‌کند و برنامه فاقد امکانات ویژوال است. در حالت GUI (سرنام  Graphic User Interface) که حالت مدرن‌تری نسبت به حالت قبل است و بیش‌تر برنامه‌های کاربردیتان را با استفاده از آن می‌سازید، رابط کاربری به‌صورت گرافیکی است و از ماوس می‌توانید استفاده کنید.
2 – یکی از فناوری‌های مفید که با استفاده از آن امکان شبیه‌سازی دستورالعمل‌های معماری 32 بیتی به 64 بیتی را فراهم می‌کند. در نتیجه برنامه‌های 32 بیتی روی آن‌ها اجرا می‌شود. با این تکنیک ساختارهای 32 بیتی به 64 بیتی طراز می‌شوند.

 

جوابی بنویسید

ایمیل شما نشر نخواهد شدخانه های ضروری نشانه گذاری شده است. *

*

استفاده از مطالب سايت تنها با ذکر منبع بلامانع است. کليه حقوق اين سايت متعلق به شبکه پرداز می باشد.