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 (
windowbrauzerda,globalNode'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:
- Barcha "tirik" (reachable) obyektlar
From-space'danTo-space'ga ko'chiriladi From-space'da qolgan hamma narsa (ko'chirilmagan) — butunlay tashlab yuboriladi, sekin-sekin emas, bir zumdaFromvaTospace'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).