Garbage Collector در CSharp - قسمت سوم


در قسمت قبلی درباره تفاوت‌های Stack و Heap صحبت کرده و به این نتیجه رسیدیم که برای آزادسازی حافظه Heap، در صورتی که نخواهیم اینکار را بصورت دستی انجام دهیم، نیاز به Garbage Collector پیدا خواهیم کرد.


تاریخچه ای مختصر از GC در NET.


ایده اولیه ایجاد Garbage Collector در NET. در سال 1990 بود که در آن زمان، مایکروسافت مشغول پیاده سازی خود از JavaScript بنام JScript بود. در ابتدا JScript توسط تیمی چهار نفره توسعه داده میشد و در آن زمان یکی از اعضای این تیم به نام Patrick Dussud که بعنوان پدر Garbage Collector در NET. شناخته میشود، یک Conservative GC را داخل تیم توسعه داد. در آن زمان CLR ای وجود نداشت و Patrick Dussud برروی JVM کار میکرد.

مایکروسافت سعی بر پیاده سازی نسخه ای اختصاصی از JVM برای خود بجای ایجاد چیزی شبیه به NET Runtime. فعلی داشت اما بعد از شکل گیری تیم CLR، به این نتیجه رسیدند که JVM برای آنها محدودیت هایی را ایجاد میکند و به همین دلیل شروع به ایجاد Environment خود کردند.

با این تصمیم، Patrick Dussud مجددا یک GC جدید با ایده “بهترین GC ممکن” با زبان LISP که در آن بیشترین مهارت را داشت، بصورت Prototype نوشت و سپس یک Transpiler از LISP به ++C نوشت که کدهای آن قابل استفاده در Runtime مایکروسافت باشد.

کدهای فعلی مربوط به Garbage Collector مورد استفاده در NET. در این فایل از ریپازیتوری runtime مایکروسافت قابل دسترسی هستند. در حال حاضر خانوم Maoni Stephens مدیر فنی تیم GC مایکروسافت هستند که کنفرانس‌ها و مقالات زیادی نیز درباره نکات مختلف پیاده سازی GC در بلاگ خود نوشته و ارائه کرده اند.


در حال حاضر، سه حالت (flavor) از GC در NET. تعبیه شده است که هرکدام از این حالات برای انواع مختلفی از برنامه‌ها بهینه شده است که در ادامه به بررسی آنها میپردازیم.


Server GC

این نوع GC برای برنامه‌های سمت سرور نظیر ASP.NET Core و WCF بهینه سازی شده است که تعداد ریکوئست‌های زیادی به آنها وارد میشود و هر ریکوئست باعث allocate شدن اشیا مختلفی شده و بطور کلی، نرخ allocation و deallocation در آنها بالاست.

Server GC به ازای هر پردازنده، از یک Heap و یک GC Thread مجزا استفاده میکند. این بدین معناست که اگر شما یک پردازنده با هشت Core داشته باشید، در زمان Garbage Collection، روی هرکدام از Coreها یک Heap و GC Thread مستقل وجود دارد که عمل Garbage Collection را انجام میدهند.

این شکل عملکرد باعث میشود که Collection در سریعترین زمان ممکن بدون وقفه اضافه انجام شود و برنامه شما اصطلاحا ((Freeze)) نشود.

Server GC فقط روی پردازنده‌های چند هسته ای قابل اجراست و اگر سعی کنید برنامه خود را روی یک سیستم با پردازنده تک هسته ای در حالت Server GC اجرا کنید، بصورت خودکار برنامه شما از Non-Concurrent Workstation GC استفاده کرده و اصطلاحا Fallback خواهد شد.

اگر نیاز دارید که در برنامه‌هایی بغیر از Server-Side Applicationها، نظیر WPF و Windows Service‌ها و … از این نوع GC استفاده کنید (به شرط چند هسته بودن پردازنده)، میتوانید این تنظیمات را به فایل app.config یا web.config خود اضافه کنید:

<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

همچنین در برنامه‌های NET Core.ای نیز میتوانید این تنظیمات را داخل فایل csproj. برنامه خود اضافه کنید:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Concurrent Workstation GC

این حالت، حالت پیشفرض مورد استفاده در برنامه‌های Windows Forms و Windows Service است. این حالت از GC برای برنامه هایی بهینه شده است که در آنها، هنگام وقوع Garbage Collection، برنامه توقف و مکث حتی چند لحظه ای نداشته و Collection باعث نشود که کاربر نتواند روی یک دکمه کلیک کند و اصطلاحا برنامه ((Unresponsive)) شود.

برای فعالسازی Concurrent Workstation GC این تنظیمات را داخل config برنامه خود باید اعمال کنید:

<configuration>
  <runtime>
    <gcConcurrent enabled="true" />
  </runtime>
</configuration>

Non-Concurrent Workstation GC

این حالت شبیه به حالت Server GC است با این تفاوت که عمل Collection روی Thread ای که درخواست allocate کردن یک object را کرده است، صورت میگیرد.

برای مثال:

Thread شماره یک درخواست allocate کردن یک string با طول 10000 کاراکتر را میدهد.

حافظه فضای کافی برای تخصیص این حجم از حافظه را نداشته و سعی میکند با اجرای Garbage Collector، این حجم فضای مورد نیاز از حافظه را خالی کند.

CLR تمام Thread‌های برنامه را متوقف میکند و Garbage Collector شروع به کار کرده و اشیا بلااستفاده روی Thread ای که آن را فراخوانی کرده است را Collect میکند.

بعد از پایان Collection، تمامی Threadهای برنامه که در مرحله قبل متوقف شده بودند، مجددا شروع به کار خواهند کرد.

این حالت از GC برای برنامه‌های Server-Side ای که برروی پردازنده تک هسته ای اجرا میشوند، پیشنهاد میشود. برای فعالسازی این حالت، تنظیمات داخل config برنامه به این صورت باید تغییر پیدا کند:

<configuration>
  <runtime>
    <gcConcurrent enabled="false" />
  </runtime>
</configuration>

این جدول کمک خواهد کرد که بر اساس نوع برنامه خود، تنظیمات درست را برای GC اعمال نمایید (در اکثر موارد، تنظیمات پیشفرض بهترین انتخاب بوده و نیازی به تغییر روند کار GC نیست):

Concurrent Workstation Non-Concurrent Workstation Server GC
Design Goal Balance throughput and responsiveness for client apps with UI. Maximize throughput on single-processor machines. Maximize throughput on multi-processor machines for server apps that create multiple threads to handle the same types of requests.
Number of Heaps 1 1 1 per processor ( hyper thread aware )
GC Threads The thread which performs the allocation that triggers the GC. The thread which performs the allocation that triggers the GC. 1 dedicated GC thread per processor
Execution Engine Suspension EE is suspended much shorter but several times during a GC. EE is suspended during a GC. EE is suspended during a GC.
Config Setting
On a single processor (fallback) Non-Concurrent Workstation GC