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
-
Aufräummuster verwenden
class Entsorgbar { entsorgen() { // Ressourcen aufräumen } }
-
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); } };
-
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
-
Node.js --inspect
node --inspect app.js # Chrome DevTools für Speicheranalyse verbinden
-
Heap-Dump-Analyse
const heapdump = require('heapdump'); // Heap-Snapshot erstellen heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
-
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); } }