Garbage Collection و شناسایی الگوهای رایج Memory Leak

در درس قبل با مفهوم Reachability آشنا شدید: معیاری که مشخص می‌کند آیا یک داده هنوز در حافظه لازم است یا نه. در این درس، مکانیزمی که واقعاً بر اساس همین معیار عمل می‌کند بررسی می‌شود—Garbage Collection—و سپس چند الگوی رایج که علی‌رغم وجود این مکانیزم خودکار، همچنان می‌توانند به نشتی حافظه (Memory Leak) منجر شوند.

Garbage Collector چیست؟

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

function createLargeArray() {
  const bigArray = new Array(1000000).fill("داده");
  return bigArray.length;
}

createLargeArray();
// پس از پایان تابع، bigArray دیگر از هیچ‌جایی reachable نیست
// Garbage Collector در زمان مناسب، حافظه‌ی آن را آزاد می‌کند

نکته‌ی مهم این است که برنامه‌نویس نمی‌تواند دقیقاً پیش‌بینی کند Garbage Collector چه زمانی حافظه را آزاد می‌کند؛ این تصمیم کاملاً به موتور جاوااسکریپت (مثل V8 در کروم) سپرده شده و بر اساس الگوریتم‌های داخلی آن انجام می‌شود.

الگوریتم پایه: Mark-and-Sweep

اکثر موتورهای مدرن جاوااسکریپت از الگوریتمی به نام Mark-and-Sweep استفاده می‌کنند که به‌طور خلاصه شامل دو مرحله است: ابتدا از تمام ریشه‌ها (مثل متغیرهای سراسری) شروع کرده و هر داده‌ی reachable را «علامت‌گذاری» می‌کند؛ سپس تمام داده‌هایی که علامت نخورده‌اند (یعنی unreachable هستند) را از حافظه پاک می‌کند.

Memory Leak: وقتی داده‌ی غیرضروری همچنان reachable می‌ماند

با وجود Garbage Collector، همچنان امکان بروز نشتی حافظه وجود دارد؛ نه به این دلیل که Garbage Collector اشتباه می‌کند، بلکه چون برنامه‌نویس به‌طور ناخواسته ارجاعی را باقی می‌گذارد که داده‌ای دیگر لازم نیست را همچنان reachable نگه می‌دارد.

الگوی رایج شماره ۱: تایمرهای پاک‌نشده

همان‌طور که در فصل ۱۷ اشاره شد، فراموش‌کردن پاک‌سازی setInterval یکی از رایج‌ترین منابع نشتی حافظه است، چون تابع callback و تمام متغیرهایی که آن تابع به آن‌ها ارجاع دارد، تا زمان فراخوانی clearInterval reachable باقی می‌مانند:

نشتی حافظه با setInterval فراموش‌شده
خروجی کنسول اینجا نمایش داده می‌شود...

الگوی رایج شماره ۲: Event Listener های حذف‌نشده

اگر شنونده‌ی رویدادی روی یک عنصر ثبت شود که ارجاعی به داده‌ای حجیم دارد، و آن عنصر بعداً از DOM حذف شود اما شنونده هرگز با removeEventListener پاک نشود، آن داده همچنان reachable و در حافظه باقی می‌ماند:

function attachHandler() {
  const largeCache = new Array(500000).fill("داده‌ی سنگین");
  const button = document.querySelector("#myButton");

  button.addEventListener("click", () => {
    console.log(largeCache.length);
  });

  // اگر بعداً button.remove() اجرا شود اما listener پاک نشود،
  // در برخی مرورگرها ممکن است largeCache همچنان در حافظه باقی بماند
}

الگوی رایج شماره ۳: متغیرهای سراسری ناخواسته

از آنجا که متغیرهای سراسری همیشه به‌عنوان یکی از ریشه‌های اصلی reachability در نظر گرفته می‌شوند، هر داده‌ای که به‌طور تصادفی به یک متغیر سراسری تبدیل شود، هرگز توسط Garbage Collector پاک نمی‌شود، حتی اگر برنامه دیگر به آن نیازی نداشته باشد:

function processData() {
  // فراموش‌کردن let/const/var، متغیر را به‌طور ناخواسته سراسری می‌کند
  leakedData = new Array(1000000).fill("نشت");
}

processData();
// leakedData اکنون یک متغیر سراسری است و برای همیشه در حافظه باقی می‌ماند

همان‌طور که در فصل مربوط به ماژول‌ها اشاره شد، این نوع خطا در حالت strict (که کد داخل ماژول‌های ES به‌طور خودکار دارند) به‌جای ایجاد متغیر سراسری، مستقیماً خطا تولید می‌کند؛ یکی دیگر از دلایلی که استفاده از ماژول‌ها را در پروژه‌های واقعی ترجیح می‌دهد.

جدول خلاصه‌ی الگوهای رایج نشتی حافظه

الگو راه‌حل
تایمر پاک‌نشده فراخوانی clearInterval یا clearTimeout در زمان مناسب
Event Listener حذف‌نشده فراخوانی removeEventListener پیش از حذف عنصر
متغیر سراسری ناخواسته استفاده‌ی همیشگی از let/const و فعال‌سازی حالت strict

نکات کلیدی

  • Garbage Collector به‌طور خودکار و دوره‌ای، حافظه‌ی داده‌های unreachable را بر اساس الگوریتمی مثل Mark-and-Sweep آزاد می‌کند.
  • زمان دقیق اجرای Garbage Collection توسط برنامه‌نویس قابل کنترل یا پیش‌بینی نیست.
  • Memory Leak زمانی رخ می‌دهد که ارجاعی ناخواسته، داده‌ای غیرضروری را همچنان reachable نگه دارد.
  • تایمرهای پاک‌نشده، Event Listenerهای حذف‌نشده، و متغیرهای سراسری ناخواسته، از رایج‌ترین منابع نشتی حافظه در پروژه‌های واقعی هستند.

جمع‌بندی

Garbage Collection مکانیزمی خودکار و مبتنی بر Reachability است که مسئولیت مدیریت حافظه را از دوش برنامه‌نویس برمی‌دارد، اما این بدان معنا نیست که نشتی حافظه در جاوااسکریپت غیرممکن است. شناخت الگوهای رایجی مثل تایمرهای پاک‌نشده و Event Listenerهای حذف‌نشده، و پاک‌سازی آگاهانه‌ی این ارجاعات در زمان مناسب، تفاوت اصلی میان برنامه‌ای پایدار در بلندمدت و برنامه‌ای است که به‌مرور کندتر می‌شود.