Memory Leak — nima, qanday topiladi
Muammodan boshlaylik
Serveringiz ishga tushdi. Birinchi kunda yaxshi ishladi. Ikkinchi kunda sekinlashdi. Uchinchi kunda to'xtab qoldi. Qayta ishga tushirdingiz — yana bir necha kun ishladi, yana to'xtadi.
Monitoring grafigiga qarasangiz — xotira asta-sekin o'sib borgan, to'lib ketgan, server crash bo'lgan.
Bu — Memory Leak (xotira sizishi). Dastur ishlatgan xotirani bo'shatmaydi, u to'planib boradi.
Memory Leak nima?
Kompyuter xotirasi cheklangan. Dastur ishlayotganda xotira oladi — o'zgaruvchilar, ob'ektlar, massivlar. Ishlatib bo'lgach, xotira bo'shatilishi kerak.
JavaScript da bu avtomatik — Garbage Collector (GC) ishlatilmayotgan ob'ektlarni topib, xotirani bo'shatadi. Lekin GC faqat hech kim murojaat qilmayotgan ob'ektlarni o'chiradi. Agar ob'ektga biror yerdan havola (reference) saqlanib qolsa — GC uni o'chira olmaydi.
Ob'ekt yaratildi → ishlatildi → havola yo'qoldi → GC o'chiradi ✅
Ob'ekt yaratildi → ishlatildi → havola qoldi → GC o'chira olmaydi ❌
→ xotira to'planadi
Eng ko'p uchraydigan sabablar
1. Global o'zgaruvchilarga to'plash
Global o'zgaruvchilar dastur ishlayotgan butun vaqt davomida xotirada turadi. Ularga ma'lumot qo'shib borilsa — xotira o'sib boradi.
// Xato:
const cache = {}; // global
app.get('/user/:id', async (req, res) => {
const user = await db.getUser(req.params.id);
cache[req.params.id] = user; // har so'rovda qo'shiladi, hech qachon o'chirilmaydi
res.json(user);
});
// Bir kunda million so'rov → million foydalanuvchi xotirada
Yechim — cache hajmini cheklash yoki TTL (time-to-live) qo'yish:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300, maxKeys: 1000 });
// 5 daqiqadan keyin avtomatik o'chadi, maksimal 1000 ta kalit
2. Event listener larni o'chirmaslik
16-darsda ko'rdik: on bilan qo'shilgan tinglovchi o'chirilmasa — ob'ekt xotirada qoladi.
// Xato — har so'rovda yangi tinglovchi qo'shiladi:
app.get('/stream', (req, res) => {
emitter.on('data', (data) => {
res.write(data); // bu funksiya req va res ni "ushlab turadi"
});
// Tinglovchi hech qachon o'chirilmaydi
// req, res, data — xotirada qoladi
});
// To'g'ri:
app.get('/stream', (req, res) => {
const handler = (data) => res.write(data);
emitter.on('data', handler);
req.on('close', () => {
emitter.off('data', handler); // mijoz uzilganda o'chirish
});
});
3. Yopilmagan timer lar
setInterval to'xtatilmasa — callback va uning ichidagi hamma narsa xotirada qoladi.
// Xato:
function startMonitoring(server) {
setInterval(() => {
console.log('Server holati:', server.connections);
// server ob'ekti interval orqali ushlab turiladi
}, 1000);
// clearInterval hech qachon chaqirilmaydi
}
// To'g'ri:
function startMonitoring(server) {
const interval = setInterval(() => {
console.log('Server holati:', server.connections);
}, 1000);
server.on('close', () => clearInterval(interval)); // server yopilganda to'xtat
return interval; // tashqaridan ham to'xtatish mumkin
}
4. Closure da katta ob'ektni ushlab qolish
Closure — ichki funksiya tashqi funksiyaning o'zgaruvchilarini "eslab qoladi". Bu ba'zan kutilmagan xotira sizishiga olib keladi.
// Xato:
function processRequest(req) {
const bigData = req.body; // 10 MB ma'lumot
return function sendResponse(res) {
res.json({ status: 'ok' });
// bigData bu yerda ishlatilmaydi
// lekin closure uni xotirada ushlab turadi
};
}
// To'g'ri:
function processRequest(req) {
const result = extractNeeded(req.body); // faqat kerakli qismni ol
const bigData = null; // havolani uzish
return function sendResponse(res) {
res.json(result);
};
}
Memory Leak ni qanday topish
1. process.memoryUsage() bilan kuzatish
function logMemory(label) {
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`[${label}] Heap: ${used.toFixed(1)} MB`);
}
setInterval(() => logMemory('monitor'), 5000);
Agar xotira doimiy o'sib borsa va hech qachon tushmasa — leak bor.
[monitor] Heap: 45.2 MB
[monitor] Heap: 47.8 MB
[monitor] Heap: 51.3 MB
[monitor] Heap: 55.9 MB ← o'sib bormoqda
[monitor] Heap: 61.2 MB
2. --inspect bilan Chrome DevTools
node --inspect app.js
Chrome da chrome://inspect ga kiring. "Memory" tabida heap snapshot oling. Bir necha daqiqadan keyin yana oling. Ikkalasini solishtiring — qaysi ob'ektlar ko'payib borgan?
3. clinic.js — avtomatik tahlil
npm install -g clinic
clinic doctor -- node app.js
clinic doctor serveringizni ishga tushiradi, so'rovlar yuboradi va avtomatik tahlil qiladi. Memory leak, CPU muammolari, event loop bloklanishi — bularni vizual ko'rsatadi.
Amaliy misol — leak topish
const express = require('express');
const app = express();
// Leak bor:
const requests = [];
app.get('/', (req, res) => {
requests.push({ time: Date.now(), url: req.url }); // to'planib boradi
res.send('OK');
});
// Monitoring:
setInterval(() => {
const mb = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`Heap: ${mb.toFixed(1)} MB | Requests: ${requests.length}`);
}, 2000);
app.listen(3000);
Bir necha daqiqa so'rov yuborgandan keyin:
Heap: 12.3 MB | Requests: 0
Heap: 15.7 MB | Requests: 1240
Heap: 19.2 MB | Requests: 2480
Heap: 23.8 MB | Requests: 3720 ← o'sib bormoqda
requests massivi hech qachon tozalanmaydi — leak topildi.
Yechim:
// Faqat oxirgi 100 ta so'rovni saqlash:
if (requests.length > 100) requests.shift();
Tez-tez uchraydigan xatolar
GC ni majburlashga urinish:
// Node.js da manual GC yo'q (--expose-gc flag bilan bo'lsa ham)
// global.gc(); // ishlatmang — bu yechim emas
// To'g'ri yechim — havolalarni uzish:
let bigObject = { data: new Array(1e6) };
// ishlatib bo'lgach:
bigObject = null; // GC endi o'chira oladi
WeakMap va WeakSet ni unutish:
Ob'ektga bog'liq ma'lumot saqlash kerak bo'lsa — oddiy Map o'rniga WeakMap ishlatiladi. WeakMap da kalit ob'ekt boshqa yerdan o'chirilsa — WeakMap dan ham avtomatik o'chadi.
// Xato — ob'ekt o'chirilsa ham Map da qoladi:
const cache = new Map();
cache.set(userObj, userData);
userObj = null; // Map da hali bor!
// To'g'ri:
const cache = new WeakMap();
cache.set(userObj, userData);
userObj = null; // WeakMap dan ham o'chadi
Xulosa
Memory Leak — xotira bo'shatilmay to'planib borishi. Asosiy sabablar: global o'zgaruvchilarga cheksiz to'plash, event listener larni o'chirmaslik, yopilmagan timer lar, closure da katta ob'ektlarni ushlab qolish.
Topish uchun: process.memoryUsage() bilan kuzatish, Chrome DevTools heap snapshot, clinic.js bilan avtomatik tahlil.
Eng yaxshi yechim — profilaktika: event listener larni o'chirish, cache hajmini cheklash, timer larni to'xtatish.
Keyingi dars
24-dars: PM2 — process boshqarish, cluster mode, monitoring