Bootcamp i backendutvikling med Node.js · leksjon

Innhenting og sammenligning av heap-snapshots

Finn objekter som holdes i minnet og kilder til lekkasjer ved å sammenligne heap-snapshots i inspektøren.

Leksjon 2 av 413 trinn

Innhenting og sammenligning av heap-snapshots er en gratis leksjon i Bootcamp i backendutvikling med Node.js på CoddyKit. Dette er leksjon 2 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med Node.js, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hvorfor heap-snapshots er viktige

Et heap-snapshot er en komplett dump av hvert JavaScript-objekt som lever i V8 idet De tar snapshotet. For en Node.js-backend er dette det mest presise verktøyet for å besvare ett spørsmål: hva holdes fortsatt tilbake, og hvorfor?

  • En prosess med en lekkasje fortsetter å allokere objekter som aldri blir samlet inn, fordi noe fortsatt holder en referanse til dem.
  • Én snapshot viser hva som finnes nå, mens en sammenligning av to snapshots over tid viser hva som vokser – og det er det egentlige lekkasjesignalet.

I denne leksjonen skal De ta snapshots fra en kjørende Node-tjeneste, laste dem inn i Chrome DevTools, sammenligne dem og følge retainer-kjeden tilbake til koden som forårsaker problemet.

Eksponere Inspector

For å ta snapshots fra en faktisk tjeneste må De først koble til V8 Inspector. Start prosessen med --inspect slik at DevTools (eller inspector-protokollen) kan koble seg til.

  • node --inspect server.js åpner Inspector på 127.0.0.1:9229.
  • Åpne chrome://inspect i Chrome, klikk på inspect for målet, og gå deretter til fanen Memory.
  • Bind aldri Inspector til et offentlig grensesnitt i produksjon – det gir full tilgang til kodekjøring.

Utdraget nedenfor er det nøyaktige CLI-kallet De ville lagt inn i startkommandoen.

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

Ta et snapshot programmatisk

De kan ikke alltid åpne DevTools mot en produksjonsserver. Den innebygde v8-modulen kan skrive en .heapsnapshot-fil til disk på forespørsel, som De senere kan laste inn i DevTools uten tilkobling.

  • v8.writeHeapSnapshot(filename) serialiserer hele heapen synkront.
  • Utløs dette fra en signalbehandler eller en intern administratorrute, slik at De kan hente snapshots uten å starte tjenesten på nytt.

Dette programmet er helt selvstendig – det skriver et snapshot og avslutter.

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');

Utløse snapshots med et signal

Et vanlig produksjonsmønster er å lytte etter et OS-signal og dumpe et snapshot uten å berøre den kjørende tjenesten. De sender kill -USR2 <pid>, og prosessen skriver en fil.

  • SIGUSR2 er konvensjonelt ledig for bruk av applikasjoner (Nodemon bruker det til omstarter, så velg et annet signal hvis De kjører Nodemon).
  • Ta alltid høyde for GC: Snapshots inneholder objekter som ikke kan nås, frem til neste GC. Ta derfor snapshotet etter at heapen har stabilisert seg.
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);

Teknikken med tre snapshots

Et enkelt snapshot inneholder mye støy – det inneholder alt, også legitime cacher som skal leve lenge. Den klassiske oppskriften for å finne lekkasjer er teknikken med tre snapshots:

  • Snapshot 1 – utgangspunktet, rett etter oppvarming.
  • Kjør den mistenkte kodebanen mange ganger (for eksempel ved å kalle et endepunkt 1 000 ganger).
  • Snapshot 2 – etter belastningen.
  • Kjør belastningen én gang til, og ta deretter Snapshot 3.

Objekter som finnes i Snapshot 2 og fortsatt finnes i Snapshot 3, er den reelle lekkasjen – midlertidige forespørselsobjekter vil ha blitt samlet inn innen da.

Tvinge GC for rene snapshots

DevTools kjører automatisk en full GC før hvert snapshot, slik at minnet De ser, faktisk bare inneholder objekter som kan nås. Når De tar snapshots programmatisk, bør De gjøre det samme for å unngå å telle med søppel som snart forsvinner.

  • Kjør Node med --expose-gc for å gjøre global.gc() tilgjengelig.
  • Kall global.gc() to ganger før De tar snapshotet – den andre gjennomgangen rydder opp i objekter som ble frigjort under den første.
// 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'));

Lese sammendragsvisningen

Last inn en .heapsnapshot i Memory-fanen i DevTools. Standardvisningen Summary grupperer objekter etter konstruktør.

  • Objects Count – hvor mange levende instanser av konstruktøren som finnes.
  • Shallow Size – minnet selve objektet bruker, uten det som refereres til fra objektet.
  • Retained Size – minnet som ville blitt frigjort dersom objektet ble slettet, inkludert alt som bare dette objektet holder levende. Dette er tallet som betyr noe ved lekkasjer.

Sorter etter Retained Size i synkende rekkefølge for å finne objektene som dominerer heapen.

Comparison-visning: sammenligne to snapshots

Den virkelige styrken ligger i visningen Comparison. Når De har lastet inn to snapshots, bytter De nedtrekkslisten fra Summary til Comparison og velger utgangspunktet som sammenligningsgrunnlag.

  • #New – objekter som er allokert siden utgangspunktet.
  • #Deleted – objekter som er samlet inn siden utgangspunktet.
  • #Delta – nettoendringen. En konstruktør med en stor positiv delta som fortsetter å vokse mellom sammenligningene, er lekkasjen.
  • Size Delta – netto økning i bibeholdte byte.

Fokuser på positive deltaer der antallet øker i takt med iterasjonene i belastningen.

Bygge en reproduserbar lekkasje

For å øve på arbeidsflyten trenger De en deterministisk lekkasje. En array på modulnivå (eller en Map) som De fortsetter å legge elementer i – uten å tømme den – er det klassiske eksempelet. Hver forespørsel legger til data som aldri kan samles inn.

  • leaked-arrayen kan nås fra modulomfanget, så V8 må beholde hver oppføring.
  • I DevTools vises dette som en voksende (array) eller som konstruktøren for lukningen Deres, med en delta som øker.

Dette selvstendige programmet lekker med hensikt og skriver ut heap-veksten.

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));

Følge retainer-kjeden

Når De har funnet en mistenkelig konstruktør, klikker De på den for å utvide instansene og undersøker deretter ruten Retainers nederst. Retainers besvarer det eneste spørsmålet som betyr noe: hvem holder dette objektet levende?

  • Kjeden går fra objektet opp til en GC root (det globale objektet, en modul-lukning, et Promise som fortsatt venter, en aktiv tidtaker og så videre).
  • En gulmarkert node kan nås direkte fra en JS-variabel. Rødt markerer en frakoblet DOM-lignende node (sjeldent i Node).
  • Følg kjeden til De kjenner igjen Deres variabel – det er kodelinjen som må rettes.

Lukninger over store omfang, cacher uten størrelsesbegrensning og EventEmitter-lyttere som aldri fjernes, er de vanligste årsakene.

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'));

Bekrefte løsningen med en siste sammenligning

Etter at lekkasjen er rettet, bør De bevise det med den samme rutinen med tre snapshots. Kjør den identiske belastningen på nytt og sammenlign utgangspunktet med snapshotet etter belastningen.

  • Konstruktøren som tidligere vokste, bør nå vise en delta nær null – allokeringer og innsamlinger balanserer hverandre.
  • Bruk en WeakMap eller en LRU-cache med begrenset størrelse, slik at oppføringer kan samles inn når det ikke lenger finnes referanser til dem.
  • Automatiser en sikkerhetskontroll: hev at heapUsed etter GC holder seg under en terskel gjennom N iterasjoner i en utholdenhetstest.
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');

Hurtigsjekk

De tar tre heap-snapshots ved hjelp av standardteknikken for å finne lekkasjer. Hvilke objekter er de sterkeste bevisene på en reell minnelekkasje?

Oppsummering

De har nå en komplett arbeidsflyt for å finne lekkasjer ved hjelp av heap-snapshots i Node.js-backender:

  • Ta snapshots via --inspect + DevTools, v8.writeHeapSnapshot() eller en SIGUSR2-behandler i produksjon.
  • Tving GC (--expose-gc, to kall til global.gc()) slik at snapshots bare gjenspeiler minne som kan nås.
  • Bruk teknikken med tre snapshots for å skille reelle lekkasjer fra midlertidige allokeringer.
  • Let etter økende #Delta-verdier i visningen Comparison, og sorter etter Retained Size.
  • Følg retainer-kjeden til GC-roten for å finne den nøyaktige variabelen som forårsaker problemet – vanligvis en cache uten størrelsesbegrensning, en vedvarende lukning eller en lytter som ikke er fjernet.
  • Bekreft løsningen med en ny sammenligning som viser en flat delta. Foretrekk WeakMap eller cacher med begrenset størrelse.
Gratis å komme i gang

Lær deg JavaScript med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
22
Leksjoner
92

Ofte stilte spørsmål

Er leksjonen «Innhenting og sammenligning av heap-snapshots» gratis?

Ja – hele teksten i «Innhenting og sammenligning av heap-snapshots» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Bootcamp i backendutvikling med Node.js-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hva lærer jeg i «Innhenting og sammenligning av heap-snapshots»?

Finn objekter som holdes i minnet og kilder til lekkasjer ved å sammenligne heap-snapshots i inspektøren. Du øver på Bootcamp i backendutvikling med Node.js med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med Node.js?

Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med Node.js på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Innhenting og sammenligning av heap-snapshots»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med Node.js-leksjonen?

Ja. Alle Bootcamp i backendutvikling med Node.js-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. V8-heapen, generasjonsbasert GC og objektenes levetid
  2. Innhenting og sammenligning av heap-snapshots
  3. CPU-profilering og flame graphs for varme kodebaner
  4. Oppdagelse og løsning av vanlige lekkasjemønstre
← Tilbake til Bootcamp i backendutvikling med Node.js