Foruma hoş geldin 👋, Ziyaretçi

Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

Node.js'te memory leak oluşmasını engellemek için closure ve event listener'ları nasıl düzgün temizlerim?

pixero

Üye
Katılım
14 Mart 2026
Mesajlar
10
Merhaba arkadaşlar, bugün uzun süreli çalışan bir Node.js sunucumda karşılaştığım ve RAM'in yavaş yavaş şişip sunucuyu çökerten bir memory leak sorununu nasıl çözdüğümü anlatacağım. Bu hatayı ilk gördüğümde, uygulama başlangıçta gayet stabilken, birkaç gün sonra "JavaScript heap out of memory" hatası verip kapanıyordu. İşin köküne indiğimde, sorunun düzgün temizlenmeyen closure'lar ve event listener'lar olduğunu gördüm.

🔥 Sorunun Kökeni: Görünmez Bağlar

Node.js'in güçlü hafıza yönetimi (garbage collection) var, ama bir nesneye referans varsa onu temizleyemez. Özellikle event dinleyicileri (listener) veya bir closure içinde yakalanan (captured) değişkenler, beklenmedik referanslar oluşturabilir. En yaygın senaryo, bir olayı dinleyen bir sınıf örneğinin (instance), kendisini dinleyen olaydan kaldırmadan (remove) bellekten silinmesi. Bu durumda garbage collector, olay dinleyicisi hala aktif olduğu için o örneği temizleyemez ve bellek sürekli büyür.

🔧 Çözüm: Temizlik Rutinleri Oluşturmak

Çözüm, nesnelerin ömrü bittiğinde tüm dış bağlantılarını (external references) kendi elleriyle koparması. İşte benim kullandığım en temiz çözüm yapıları.

İlk olarak, bir EventEmitter kullanan bir sınıfınız varsa, mutlaka bir destroy() veya cleanup() metodu implemente edin ve bu metodu, örneği kullanmayı bırakmadan hemen önce çağırın.

JavaScript:
const EventEmitter = require('events');

class ConnectionManager extends EventEmitter {
  constructor(databaseUrl) {
    super();
    this.dbUrl = databaseUrl;
    this.internalTimer = null;
    this.externalEventListener = null;

    // ⚠️ DİKKAT: Bu listener, 'this' context'ini yakalar (capture).
    this.on('dataReceived', this.handleData);

    // ⚠️ DİKKAT: Bir dış modülün event'ine dinleyici ekliyoruz.
    someExternalModule.on('update', this.externalUpdateHandler.bind(this));

    this.startInternalProcess();
  }

  handleData(data) {
    console.log('İşlenen veri:', data);
  }

  externalUpdateHandler(update) {
    console.log('Dış güncelleme:', update, 'Bağlantı:', this.dbUrl);
  }

  startInternalProcess() {
    this.internalTimer = setInterval(() => {
      this.emit('heartbeat');
    }, 5000);
  }

  // 🧹 İŞTE KRİTİK TEMİZLİK METODU
  destroy() {
    // 1. Kendi üzerindeki event listener'ları kaldır.
    this.removeAllListeners('dataReceived');
    // Veya spesifik listener'ı kaldır:
    // this.off('dataReceived', this.handleData);

    // 2. Dış modüllere eklediğimiz listener'ları kaldır.
    if (someExternalModule && this.externalUpdateHandler) {
      someExternalModule.off('update', this.externalUpdateHandler);
    }

    // 3. Interval ve timeout'ları temizle.
    if (this.internalTimer) {
      clearInterval(this.internalTimer);
      this.internalTimer = null;
    }

    // 4. Büyük veri yapılarını boşalt.
    this.dbUrl = null;

    console.log('ConnectionManager temizlendi.');
  }
}

// Kullanım
const manager = new ConnectionManager('mysql://localhost:3306/db');
// ... işlemler yap ...
manager.destroy(); // Artık garbage collector bu örneği güvenle silebilir.

💡 İkinci Katman: WeakRef ve FinalizationRegistry (Node 14+)

Eğer durum daha karmaşıksa ve destroy() metodunu çağırmayı unutma riskiniz varsa, WeakRef ve FinalizationRegistry ile bir güvenlik ağı oluşturabilirsiniz. Bu, son çare olarak düşünülmeli, çünkü garbage collector'un ne zaman çalışacağı belli olmaz.

JavaScript:
// Örnek: Harici bir kaynağa zayıf referans tutma ve temizlik kaydı
class ResourceHolder {
  constructor(externalResource) {
    // Asıl kaynağa ZAYIF bir referans tutuyoruz.
    this.weakResource = new WeakRef(externalResource);
    this.resourceId = Math.random();

    // FinalizationRegistry, nesne garbage collect edildiğinde bir callback çalıştırır.
    const registry = new FinalizationRegistry((heldValue) => {
      console.log(`Resource ${heldValue} için temizlik yapılıyor...`);
      // Burada, weakResource üzerinden hala erişilebilirse temizlik yapmaya ÇALIŞABİLİRİZ.
      // Ancak %100 güvenilir değildir, sadece yardımcıdır.
    });

    // Bu örneği (this) kayıt defterine kaydediyoruz.
    // Örnek garbage collect edildiğinde, id değeri callback'e parametre olarak gider.
    registry.register(this, this.resourceId);
  }

  // Tercih edilen yöntem hala manuel destroy()
  manualCleanup() {
    const resource = this.weakResource.deref();
    if (resource) {
      // Hala erişilebilirse, manuel temizlik yap.
      resource.removeListeners?.();
    }
  }
}

Sonuç olarak, Node.js'te memory leak'i engellemenin anahtarı disiplinli bir kaynak yönetimi. Her "bağlanma" işleminin (on, addEventListener, setInterval) karşılığında bir "ayrılma" işlemi (off, removeListener, clearInterval) planlamalısınız. Bu, özellikle uzun ömürlü (long-lived) sunucu uygulamalarında hayati önem taşıyor.

Siz de Node.js projelerinizde benzer memory leak sorunları yaşadınız mı? Hangi araçlarla (örneğin heap snapshot) tespit ettiniz? Sizin kullandığınız farklı bir temizlik pattern'i var mı? Yorumlarda paylaşalım!
 

Tema özelleştirme sistemi

Bu menüden forum temasının bazı alanlarını kendinize özel olarak düzenleye bilirsiniz.

Zevkine göre renk kombinasyonunu belirle

Tam ekran yada dar ekran

Temanızın gövde büyüklüğünü sevkiniz, ihtiyacınıza göre dar yada geniş olarak kulana bilirsiniz.

Geri