Series: JavaScript Basics Lesson 62

Garbage Collection

Garbage Collection nima ekani, reachability tushunchasi va reference counting'dan nega ustunligi, V8'ning Young/Old Generation xotira bo'linishi, Scavenger va Mark-Sweep-Compact algoritmlari, va Stop-the-world muammosini yumshatish texnikalari.

62-dars

Garbage Collection

Garbage Collection (GC) — JavaScript dvigateli (V8) tomonidan avtomatik bajariladigan jarayon: dastur ishlatmay qolgan xotirani (obyektlar, massivlar, funksiyalar egallagan joyni) topib, uni avtomatik ravishda bo'shatish.

C yoki C++ kabi tillarda dasturchi xotirani qo'lda boshqaradi (malloc / free). JavaScript'da bunday emas — siz new Object() yaratasiz, lekin uni hech qachon o'zingiz o'chirmaysiz. Buni V8 ichidagi maxsus mexanizm — Garbage Collector — sizning o'rningizga qiladi.

function create() {
  const obj = { katta: "malumot" };
  return obj;
}

let x = create(); // obj xotirada, chunki x uni ushlab turibdi
x = null;        // endi hech kim obj'ni ushlamayapti
// GC keyinroq shu obj'ni xotiradan tozalaydi — avtomatik, siz chaqirmaysiz

1. Nega bu kerak?

Har bir o'zgaruvchi, obyekt, funksiya — kompyuter RAM'ida joy egallaydi. Agar dastur ishlatib bo'lgan xotirani hech kim bo'shatmasa, dastur vaqt o'tishi bilan tobora ko'proq RAM yeyveradi va oxir-oqibat qulab tushadi (bu holat keyingi mavzu — Memory leak — aynan shu narsa).

GC yo'q bo'lganda, dasturchi har bir obyekt uchun "bu endi kerakmi, yo'qmi" deb qo'lda kuzatib borishi kerak bo'lardi — bu juda xato-qiluvchan ish. GC shu yukni dasturchidan olib, dvigatelga yuklaydi.


2. Asosiy tushuncha: "Reachability" (yetib borish mumkinligi)

V8 obyektni "kerak emas" deb hisoblashda "foydalanilmayapti" degan mezondan foydalanmaydi — chunki buni aniq bilib bo'lmaydi (obyekt kelajakda kerak bo'lib qolishi mumkin). Buning o'rniga V8 reachability (yetib borish mumkinligi) mezonidan foydalanadi:

Agar biror obyektga root'lardan (asosiy nuqtalardan) boshlab, zanjir orqali yetib borib bo'lmasa — u obyekt "garbage" (chiqindi) hisoblanadi.

Root'lar — bular hech qachon o'zi "garbage" bo'lmaydigan boshlang'ich nuqtalar:

  • Global obyekt (window brauzerda, global Node'da)
  • Hozir bajarilayotgan funksiyaning local o'zgaruvchilari va parametrlari (call stack'dagi)
  • Closure orqali ushlab turilgan o'zgaruvchilar
let a = { name: "A" };
let b = { name: "B" };

a.friend = b; // a → b (b'ga a orqali yetib bo'ladi)
b.friend = a; // b → a (a'ga b orqali yetib bo'ladi)

a = null;
b = null;
// Endi ikkalasi ham bir-birini "ushlab turibdi" (o'zaro bog'liq),
// lekin root'dan (global/stack) ULARGA yetib bo'lmaydi.
// Shuning uchun ikkalasi ham "garbage" — reference count emas, reachability aniqlaydi

Bu misol muhim: eski, sodda GC algoritmlari (reference counting — "nechta joy bu obyektni ko'rsatib turibdi" deb sanash) bunday circular reference (aylanma bog'lanish) holatida ishlamay qoladi, chunki a va b hali ham bir-birini ko'rsatib turibdi (count > 0). V8 esa reference counting emas, reachability asosida ishlaydi — shuning uchun bu holatni to'g'ri aniqlaydi.


3. V8 darajasida — GC qanday ishlaydi (ichki mexanizm)

V8'da xotira ikki asosiy qismga bo'lingan — bu "Generational Hypothesis" (avlodlar gipotezasi) deb ataladigan kuzatuvga asoslangan: "ko'pchilik obyektlar yaratilgandan keyin darhol keraksiz bo'lib qoladi, kam qismi esa uzoq yashaydi".

V8 Heap (xotira)
├── Young Generation (yosh avlod) — kichik, tez tozalanadi
│     ├── Nursery (yangi obyektlar shu yerda tug'iladi)
│     └── Intermediate
└── Old Generation (keksa avlod) — katta, kamroq tez-tez tozalanadi

1. Young Generation — Scavenger algoritmi (Minor GC)

Har bir yangi obyekt avval Young Generation'ga tushadi (chunki statistik ravishda ko'pchilik obyektlar tez o'ladi — masalan, funksiya ichidagi vaqtinchalik o'zgaruvchilar).

Young Generation ikkiga bo'lingan: From-space va To-space. Scavenger algoritmi shunday ishlaydi:

  1. Barcha "tirik" (reachable) obyektlar From-space'dan To-space'ga ko'chiriladi
  2. From-space'da qolgan hamma narsa (ko'chirilmagan) — butunlay tashlab yuboriladi, sekin-sekin emas, bir zumda
  3. From va To space'lar o'rinlarini almashtiradi

Bu jarayon juda tez — chunki Young Generation kichik (odatda bir necha MB) va ko'pincha bo'sh bo'ladi.

2. Old Generation — Mark-Sweep-Compact algoritmi (Major GC)

Agar obyekt Young Generation'da bir necha marta "Scavenge"dan omon qolsa (ya'ni uzoq yashayotgani ma'lum bo'lsa) — u Old Generation'ga ko'chiriladi (bu jarayon "promotion" deyiladi).

Old Generation uchun boshqa algoritm ishlatiladi, chunki bu yerda obyektlar ko'p va ularni ko'chirish qimmatga tushadi:

  • Mark phase (belgilash): root'lardan boshlab, yetib boriladigan hamma obyekt "tirik" deb belgilanadi
  • Sweep phase (tozalash): belgilanmagan (reachable bo'lmagan) obyektlar egallagan joy — bo'sh deb belgilanadi
  • Compact phase (siqish): xotirada bo'sh joylar parchalanib qolmasligi uchun (fragmentation), tirik obyektlar bir joyga yig'ib qo'yiladi

3. Nega ikki xil algoritm?

Chunki trade-off bor: Young Gen'da tezlik muhim (ko'p, kichik, tez-o'luvchi obyektlar), Old Gen'da esa xotirani tejash va fragmentatsiyani oldini olish muhim (kam, katta, uzoq-yashovchi obyektlar). Bitta universal algoritm ikkalasini ham optimal qila olmaydi.


4. "Stop-the-world" muammosi va uning yechimi

Eng katta muammo: GC ishlayotganda, u obyektlarni tekshirish uchun butun dasturni to'xtatishi kerak (agar dastur davom etsa, obyektlar orasidagi bog'lanish o'zgarib turadi va GC noto'g'ri xulosa chiqarishi mumkin). Bu "Stop-the-World" deyiladi — va agar bu uzoq davom etsa, foydalanuvchi sahifada "friz" (qotib qolish) ni his qiladi.

V8 buni yumshatish uchun bir necha texnika ishlatadi:

  • Incremental marking — "Mark" jarayonini bir necha kichik bo'lakka bo'lib, JS ijrosi orasida navbat bilan bajarish (to'liq to'xtatish o'rniga)
  • Concurrent sweeping — "Sweep" jarayonini asosiy thread'dan alohida, fon (background) thread'da bajarish
  • Lazy sweeping — xotirani darhol emas, kerak bo'lganda tozalash

Shu texnikalar tufayli zamonaviy V8'da GC "friz"lari sezilarli darajada kamaygan (2012-yillardagi eski V8'da bu jiddiy muammo edi — o'sha paytda "Orinoco" loyihasi doirasida aynan shu masala hal qilingan).