Bootcamp backendontwikkeling met Node.js · Les

Heapsnapshots vastleggen en vergelijken

Vind behouden objecten en bronnen van lekken door heapsnapshots in de inspector te vergelijken.

Les 2 van 413 stappen

Heapsnapshots vastleggen en vergelijken is een gratis Bootcamp backendontwikkeling met Node.js-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Bootcamp backendontwikkeling met Node.js. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Waarom heap-snapshots belangrijk zijn

Een heap-snapshot is een volledige dump van elk JavaScript-object dat op het moment van vastleggen in V8 leeft. Voor een Node.js-backend is dit het nauwkeurigste hulpmiddel om één vraag te beantwoorden: wat wordt nog vastgehouden en waarom?

  • Een proces met een lekkage blijft objecten toewijzen die nooit door de garbagecollector worden verzameld, omdat iets nog een verwijzing vasthoudt.
  • Eén snapshot laat zien wat er nu bestaat; twee snapshots vergelijken in de tijd laat zien wat er groeit — en dat is het daadwerkelijke signaal van een lekkage.

In deze les leg je snapshots vast van een actieve Node-service, laad je ze in Chrome DevTools, vergelijk je ze en lees je de keten van vasthouders terug tot aan de verantwoordelijke code.

De inspector beschikbaar maken

Om snapshots van een echte service vast te leggen, koppel je eerst de V8 inspector. Start het proces met --inspect, zodat DevTools (of het inspector-protocol) verbinding kan maken.

  • node --inspect server.js opent de inspector op 127.0.0.1:9229.
  • Open chrome://inspect in Chrome, klik bij je doel op inspecteren en ga vervolgens naar het tabblad Geheugen.
  • Bind de inspector in productie nooit aan een openbare interface — daarmee geef je volledige code-uitvoeringsrechten.

Het fragment hieronder is de exacte CLI-aanroep die je in je startopdracht zou opnemen.

// package.json scripts
{
  "scripts": {
    "debug": "node --inspect=127.0.0.1:9229 server.js",
    "debug:brk": "node --inspect-brk server.js"
  }
}

Programmatig een snapshot vastleggen

Je kunt niet altijd DevTools openen tegen een productiesysteem. De ingebouwde module v8 kan op verzoek een .heapsnapshot-bestand naar schijf schrijven, dat je later offline in DevTools laadt.

  • v8.writeHeapSnapshot(filename) serialiseert de volledige heap synchroon.
  • Activeer dit vanuit een signaalverwerker of een interne beheerdersroute, zodat je snapshots kunt maken zonder opnieuw te starten.

Dit programma werkt volledig zelfstandig — het schrijft een snapshot en sluit af.

const v8 = require('v8');
const path = require('path');

function takeSnapshot(label) {
  const file = path.join(process.cwd(), `heap-${label}-${Date.now()}.heapsnapshot`);
  const written = v8.writeHeapSnapshot(file);
  console.log('Snapshot written to', written);
  return written;
}

takeSnapshot('baseline');

Snapshots activeren met een signaal

Een veelgebruikt productiepatroon is luisteren naar een besturingssysteemsignaal en een snapshot wegschrijven zonder de actieve service aan te raken. Je verstuurt kill -USR2 <pid> en het proces schrijft een bestand.

  • SIGUSR2 is conventioneel beschikbaar voor gebruik door toepassingen (Nodemon gebruikt het voor herstarts, dus kies een ander signaal als je Nodemon uitvoert).
  • Houd altijd rekening met een GC: snapshots bevatten onbereikbare objecten tot de volgende GC, dus leg de snapshot nadat de heap tot rust is gekomen vast.
const v8 = require('v8');

process.on('SIGUSR2', () => {
  const file = `heap-${process.pid}-${Date.now()}.heapsnapshot`;
  v8.writeHeapSnapshot(file);
  console.log('Heap snapshot captured:', file);
});

console.log('Send: kill -USR2', process.pid);
setInterval(() => {}, 1 << 30);

De techniek met drie snapshots

Eén snapshot bevat veel ruis — alles staat erin, inclusief legitieme caches met een lange levensduur. De klassieke aanpak om een lekkage op te sporen is de techniek met drie snapshots:

  • Snapshot 1 — nulmeting, direct na het opwarmen.
  • Voer het verdachte codepad meerdere keren uit (bijvoorbeeld door een eindpunt 1.000 keer aan te roepen).
  • Snapshot 2 — na de werklast.
  • Voer de werklast opnieuw uit en maak daarna Snapshot 3.

Objecten die in Snapshot 2 verschijnen en in Snapshot 3 blijven bestaan, vormen de echte lekkage — tijdelijke aanvraagobjecten zijn dan al verzameld.

GC forceren voor schone snapshots

DevTools voert vóór elke snapshot automatisch een volledige GC uit, zodat wat je ziet werkelijk bereikbaar geheugen is. Als je programmatig snapshots maakt, moet je hetzelfde doen om te voorkomen dat je afval meet dat bijna verdwijnt.

  • Start Node met --expose-gc om global.gc() beschikbaar te maken.
  • Roep global.gc() twee keer aan voordat je een snapshot maakt — de tweede doorgang ruimt objecten op die tijdens de eerste zijn vrijgegeven.
// run with: node --expose-gc snapshot.js
const v8 = require('v8');

function cleanSnapshot(label) {
  if (global.gc) {
    global.gc();
    global.gc();
  }
  return v8.writeHeapSnapshot(`heap-${label}.heapsnapshot`);
}

console.log(cleanSnapshot('after-gc'));

De samenvattingsweergave lezen

Laad een .heapsnapshot in het tabblad Geheugen van DevTools. De standaardweergave Samenvatting groepeert objecten per constructor.

  • Aantal objecten — hoeveel actieve instanties van die constructor bestaan.
  • Ondiepe grootte — het geheugen dat het object zelf inneemt, exclusief waarnaar het verwijst.
  • Behouden grootte — het geheugen dat zou worden vrijgegeven als dit object werd verwijderd, inclusief alles wat alleen door dit object in leven wordt gehouden. Dit is het getal dat telt bij lekkages.

Sorteer aflopend op Behouden grootte om de objecten te vinden die de heap domineren.

Vergelijkingsweergave: twee snapshots vergelijken

De echte kracht zit in de weergave Vergelijken. Laad twee snapshots, schakel de keuzelijst van Samenvatting naar Vergelijken en kies de nulmeting als vergelijkingsbasis.

  • #Nieuw — objecten die sinds de nulmeting zijn toegewezen.
  • #Verwijderd — objecten die sinds de nulmeting zijn verzameld.
  • #Verschil — de nettowijziging. Een constructor met een groot positief verschil dat bij elke vergelijking blijft groeien, is je lekkage.
  • Verschil in grootte — netto toegevoegde behouden bytes.

Richt je op positieve verschillen waarvan het aantal gelijk opgaat met de iteraties van je werklast.

Een reproduceerbare lekkage opbouwen

Om deze werkwijze te oefenen, heb je een deterministische lekkage nodig. Een array (of Map) op moduleniveau waar je steeds elementen aan toevoegt — zonder hem ooit leeg te maken — is het klassieke voorbeeld. Elke aanvraag voegt gegevens toe die nooit kunnen worden verzameld.

  • De array leaked is bereikbaar vanuit de module-scope, dus V8 moet elke invoer behouden.
  • In DevTools verschijnt dit als een groeiende (array) of als je closure-constructor met een oplopend verschil.

Dit zelfstandige programma veroorzaakt expres een lekkage en drukt de heapgroei af.

const leaked = [];

function handleRequest(i) {
  // Bug: we never remove old entries
  leaked.push({ id: i, payload: 'x'.repeat(1024), ts: Date.now() });
}

for (let i = 0; i < 5000; i++) handleRequest(i);

const mb = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('Entries retained:', leaked.length);
console.log('Heap used (MB):', mb.toFixed(1));

De keten van vasthouders volgen

Als je een verdachte constructor hebt gevonden, klik je erop om instanties uit te vouwen en inspecteer je vervolgens het deelvenster Vasthouders onderaan. Vasthouders beantwoorden de enige vraag die ertoe doet: wie houdt dit object in leven?

  • De keten loopt van het object omhoog naar een GC-root (het globale object, een module-closure, een nog niet afgehandelde Promise, een actieve timer enzovoort).
  • Een geel gemarkeerde node is rechtstreeks bereikbaar vanuit een JS-variable; rood markeert een losgekoppelde DOM-achtige node (zeldzaam in Node).
  • Volg de keten totdat je jouw variable herkent — dat is de coderegel die je moet aanpassen.

Closures over grote scopes, onbegrensde caches en EventEmitter-listeners die nooit worden verwijderd, zijn de gebruikelijke boosdoeners.

const EventEmitter = require('events');
const bus = new EventEmitter();

// Leak: a new listener per call, never removed
function subscribe(userId) {
  const bigContext = { userId, cache: new Array(10000).fill(userId) };
  bus.on('tick', () => bigContext.cache[0]);
}

for (let i = 0; i < 200; i++) subscribe(i);
console.log('Listener count:', bus.listenerCount('tick'));

De oplossing bevestigen met een laatste vergelijking

Bewijs na het oplossen van de lekkage dat het probleem verholpen is met dezelfde procedure met drie snapshots. Voer exact dezelfde werklast opnieuw uit en vergelijk de nulmeting met de snapshot na de werklast.

  • De constructor die eerder groeide, hoort nu een verschil rond nul te tonen — toewijzingen en verzamelingen houden elkaar in evenwicht.
  • Gebruik een WeakMap of een begrensde LRU-cache, zodat invoeren in aanmerking komen voor GC zodra er niet meer naar wordt verwezen.
  • Automatiseer een beveiliging: controleer met een assertie dat heapUsed na GC onder een drempel blijft gedurende N iteraties in een duurtest.
const cache = new WeakMap();

function attach(req) {
  // Keyed by the request object; entry is collectable once req is gone
  cache.set(req, { processedAt: Date.now() });
}

let req1 = { id: 1 };
attach(req1);
console.log('Has entry:', cache.has(req1));
req1 = null; // now eligible for GC; WeakMap will not retain it
console.log('Reference dropped — entry can be collected');

Korte controle

Je maakt drie heap-snapshots met de standaardtechniek om lekkages op te sporen. Welke objecten vormen het sterkste bewijs van een echte geheugenlekkage?

Samenvatting

Je beschikt nu over een volledige werkwijze om geheugenlekkages in heap-snapshots van Node.js-backends op te sporen:

  • Leg snapshots vast via --inspect + DevTools, v8.writeHeapSnapshot() of een SIGUSR2-verwerker in productie.
  • Forceer GC (--expose-gc, tweemaal global.gc()), zodat snapshots alleen bereikbaar geheugen weerspiegelen.
  • Gebruik de techniek met drie snapshots om echte lekkages te onderscheiden van tijdelijke toewijzingen.
  • Zoek in de weergave Vergelijken naar oplopende aantallen bij #Verschil; sorteer op Behouden grootte.
  • Volg de keten van vasthouders naar de GC-root om de exacte variable te vinden die het probleem veroorzaakt — meestal een onbegrensde cache, een achtergebleven closure of een niet-verwijderde listener.
  • Bevestig de oplossing met een nieuwe vergelijking die een vlak verschil laat zien; geef de voorkeur aan WeakMap of begrensde caches.
Gratis beginnen

Leer JavaScript met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
22
Lessen
92

Veelgestelde vragen

Is de les “Heapsnapshots vastleggen en vergelijken” gratis?

Ja — de volledige tekst van “Heapsnapshots vastleggen en vergelijken” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Bootcamp backendontwikkeling met Node.js wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Bootcamp backendontwikkeling met Node.js bevat in totaal 4 lessen.

Wat leer ik in “Heapsnapshots vastleggen en vergelijken”?

Vind behouden objecten en bronnen van lekken door heapsnapshots in de inspector te vergelijken. Je oefent met Bootcamp backendontwikkeling met Node.js door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Bootcamp backendontwikkeling met Node.js te beginnen?

Ervaring vooraf is niet nodig. Bootcamp backendontwikkeling met Node.js op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.

Hoe lang duurt de les “Heapsnapshots vastleggen en vergelijken”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Bootcamp backendontwikkeling met Node.js?

Ja. Elke les over Bootcamp backendontwikkeling met Node.js bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. De V8-heap, generational GC en levensduur van objecten
  2. Heapsnapshots vastleggen en vergelijken
  3. CPU-profiling en flamegraphs voor hot paths
  4. Veelvoorkomende lekpatronen detecteren en oplossen
← Terug naar Bootcamp backendontwikkeling met Node.js