FixThisBug.de Logo
FixThisBug.de
🇬🇧Bugfix WissensdatenbankAnmelden
Startseite
ImpressumDatenschutzerklärung
Kategorie: runtimeSchwierigkeit: FortgeschrittenVeröffentlicht: 2024-12-18

Speicherlecks erkennen und beheben

Speicherlecks treten auf, wenn ein Programm nicht genutzten Speicher nicht freigibt, was zu verminderter Leistung und potenziellen Abstürzen führt. In JavaScript sind Speicherlecks besonders häufig in lang laufenden Anwendungen wie Web-Apps und Node.js-Servern.

Häufige Speicherleck-Muster

1. Vergessene Event-Listener

function setupHandler() { const button = document.getElementById('myButton'); // Problem: Event-Listener bleibt auch nach Entfernung der Komponente button.addEventListener('click', () => { // Etwas mit Daten tun schwereFunktion(); }); } // Lösung: Event-Listener entfernen, wenn nicht mehr benötigt function setupHandlerFixed() { const button = document.getElementById('myButton'); const handler = () => { schwereFunktion(); }; button.addEventListener('click', handler); // Aufräumfunktion zurückgeben return () => { button.removeEventListener('click', handler); }; }

2. Closures mit Referenzen

// Problem: Cache hält Referenzen unbegrenzt const cache = new Map(); function verarbeiteDaten(daten) { // Closure speichert 'daten' im Cache cache.set(daten.id, () => { return schwereBerechnung(daten); }); } // Lösung: Cache-Bereinigung implementieren const cache = new Map(); const MAX_CACHE_GROESSE = 1000; function verarbeiteDatenFixed(daten) { // Cache-Größenlimit implementieren if (cache.size >= MAX_CACHE_GROESSE) { const aeltesterKey = cache.keys().next().value; cache.delete(aeltesterKey); } cache.set(daten.id, () => { return schwereBerechnung(daten); }); // Optional: Ablaufzeit setzen setTimeout(() => { cache.delete(daten.id); }, 3600000); // 1 Stunde }

3. Zirkuläre Referenzen

// Problem: Zirkuläre Referenz verhindert Garbage Collection function erstelleZirkulaereReferenz() { let obj1 = {}; let obj2 = {}; obj1.ref = obj2; obj2.ref = obj1; return obj1; } // Lösung: WeakRef verwenden oder Referenz manuell auflösen function erstelleSchwacheReferenz() { let obj1 = {}; let obj2 = {}; obj1.ref = new WeakRef(obj2); obj2.ref = new WeakRef(obj1); return obj1; }

Erkennungswerkzeuge und -techniken

1. Chrome DevTools Memory Profiler

// Heap-Snapshot vor Operation erstellen // Verdächtige Operation ausführen // Heap-Snapshot nach Operation erstellen // Snapshots vergleichen, um behaltene Objekte zu identifizieren // Beispiel für programmatisches Auslösen von Heap-Snapshots function debugSpeicherleck() { console.log('Vor Operation'); // DevTools: Heap-Snapshot erstellen fuehreVerdaechtigeOperationAus(); console.log('Nach Operation'); // DevTools: Heap-Snapshot erstellen und vergleichen }

2. Speichernutzung überwachen

function ueberwacheSpeichernutzung() { if (process.memoryUsage) { const verwendet = process.memoryUsage(); console.log({ heapTotal: `${Math.round(verwendet.heapTotal / 1024 / 1024)} MB`, heapUsed: `${Math.round(verwendet.heapUsed / 1024 / 1024)} MB`, external: `${Math.round(verwendet.external / 1024 / 1024)} MB`, rss: `${Math.round(verwendet.rss / 1024 / 1024)} MB` }); } } // In Intervallen überwachen setInterval(ueberwacheSpeichernutzung, 1000);

Präventionsstrategien

1. WeakMap und WeakSet verwenden

// Problem: Starke Referenzen verhindern Garbage Collection const cache = new Map(); cache.set(grossesObjekt, berechnetesDaten); // Lösung: WeakMap verwenden, um Garbage Collection zu ermöglichen const cache = new WeakMap(); cache.set(grossesObjekt, berechnetesDaten); // grossesObjekt kann garbage collected werden, wenn keine anderen Referenzen existieren

2. Aufräumen in Komponenten implementieren

class Komponente { constructor() { this.listeners = new Set(); this.intervalIds = new Set(); } addEventListener(element, typ, handler) { element.addEventListener(typ, handler); this.listeners.add({ element, typ, handler }); } setInterval(callback, delay) { const id = setInterval(callback, delay); this.intervalIds.add(id); return id; } aufraeumen() { // Alle Event-Listener entfernen for (const { element, typ, handler } of this.listeners) { element.removeEventListener(typ, handler); } this.listeners.clear(); // Alle Intervalle löschen for (const id of this.intervalIds) { clearInterval(id); } this.intervalIds.clear(); } }

3. Ressourcen-Pooling

class RessourcenPool { constructor(erstelleRessource, maxGroesse = 10) { this.erstelleRessource = erstelleRessource; this.maxGroesse = maxGroesse; this.verfuegbar = []; this.inBenutzung = new Set(); } async acquire() { if (this.verfuegbar.length > 0) { const ressource = this.verfuegbar.pop(); this.inBenutzung.add(ressource); return ressource; } if (this.inBenutzung.size < this.maxGroesse) { const ressource = await this.erstelleRessource(); this.inBenutzung.add(ressource); return ressource; } throw new Error('Ressourcen-Pool erschöpft'); } release(ressource) { if (this.inBenutzung.has(ressource)) { this.inBenutzung.delete(ressource); this.verfuegbar.push(ressource); } } }

Testen auf Speicherlecks

describe('Speicherleck-Tests', () => { it('sollte beim Erstellen und Zerstören von Komponenten keinen Speicher lecken', async () => { const anfangsSpeicher = process.memoryUsage().heapUsed; // Operationen mehrmals ausführen for (let i = 0; i < 1000; i++) { const komponente = new Komponente(); komponente.macheEtwas(); komponente.aufraeumen(); } // Garbage Collection erzwingen, falls verfügbar if (global.gc) { global.gc(); } const endSpeicher = process.memoryUsage().heapUsed; const diff = endSpeicher - anfangsSpeicher; // Kleine Zunahme erlauben expect(diff).toBeLessThan(1024 * 1024); // Weniger als 1MB Wachstum }); });

Best Practices

  1. Aufräummuster verwenden

    class Entsorgbar { entsorgen() { // Ressourcen aufräumen } }
  2. Ressourcenlimits implementieren

    const LIMITIERTER_CACHE = { maxGroesse: 1000, items: new Map(), set(key, value) { if (this.items.size >= this.maxGroesse) { const aeltesterKey = this.items.keys().next().value; this.items.delete(aeltesterKey); } this.items.set(key, value); } };
  3. Speichernutzung überwachen

    class SpeicherMonitor { static warnen(schwellwert = 500) { const verwendet = process.memoryUsage().heapUsed / 1024 / 1024; if (verwendet > schwellwert) { console.warn(`Hohe Speichernutzung: ${Math.round(verwendet)}MB`); } } }

Werkzeuge für Speicheranalyse

  1. Node.js --inspect

    node --inspect app.js # Chrome DevTools für Speicheranalyse verbinden
  2. Heap-Dump-Analyse

    const heapdump = require('heapdump'); // Heap-Snapshot erstellen heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
  3. Automatisierte Speicherüberwachung

    class SpeicherWaechter { static startUeberwachung(schwellwert = 100, interval = 1000) { return setInterval(() => { const verwendet = process.memoryUsage().heapUsed / 1024 / 1024; if (verwendet > schwellwert) { console.warn(`Speicherschwellwert überschritten: ${Math.round(verwendet)}MB`); // Optional: Heap-Snapshot erstellen oder alarmieren } }, interval); } }

Selbst ausprobieren

Verbleibende Korrekturen: 10