V8:n heap, generatiivinen GC ja olioiden elinkaaret
Ymmärrä, miten V8 varaa ja kerää muistia nuoren ja vanhan sukupolven alueilla.
V8:n heap, generatiivinen GC ja olioiden elinkaaret on ilmainen Node.js-taustakehityksen bootcamp-oppitunti CoddyKitissä. Tämä on oppitunti 1/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Node.js-taustakehityksen bootcamp-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Miksi kekomuistin toiminta on tärkeää Nodessa
Jokainen Node.js-palvelun varaama objekti, sulkeuma ja puskuri sijaitsee jossain V8:n hallinnoimassa muistissa. Kun ymmärrät, missä se sijaitsee ja milloin se vapautetaan, voit erottaa palvelun, joka pysyy vakaasti 200 megatavussa, palvelusta, jonka muistinkulutus kasvaa kuormituksen alla kohti OOM-kaatumista.
V8 jakaa hallinnoidun muistinsa kahteen pääalueeseen:
- New space (nuori sukupolvi) – pieni ja nopea alue, jonne lähes jokainen objekti syntyy.
- Old space (vanha sukupolvi) – suuri alue, jonne selviävät objektit siirtyvät.
Voit tarkastella ajantasaisia lukemia ajonaikaisesti kutsulla process.memoryUsage().
// heap_snapshot.js — print V8 heap usage in MB
function mb(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
}
const u = process.memoryUsage();
console.log('rss ', mb(u.rss)); // total process memory
console.log('heapTotal ', mb(u.heapTotal)); // V8 heap reserved
console.log('heapUsed ', mb(u.heapUsed)); // V8 heap live
console.log('external ', mb(u.external)); // C++ objects bound to JS
console.log('arrayBuffers', mb(u.arrayBuffers));Heikko sukupolvihypoteesi
V8:n GC perustuu yhteen empiiriseen havaintoon, jota kutsutaan heikoksi sukupolvihypoteesiksi: useimmat objektit kuolevat nuorina.
Tyypillisessä pyyntökäsittelijässä varaat pyyntökohtaisia objekteja – jäsennettyjä JSON-runkoja, väliaikaisia taulukoita ja välimerkkijonoja – joista tulee roskaa heti vastauksen lähettämisen jälkeen. Pieni vähemmistö (välimuistit, yhteyspoolit ja moduulitason singletonit) säilyy koko prosessin eliniän.
Tämän vinouman ansiosta V8 voi optimoida toimintaansa tehokkaasti: se kerää nuoren sukupolven usein ja edullisesti ja vanhan sukupolven harvoin ja perusteellisesti. Kullekin sukupolvelle on oma sen säilymisprofiiliinsa optimoitu GC-algoritmi.
New space: scavenge-keräys ja puolitilat
New spacen kerää Scavenger, joka on Cheneyn algoritmia käyttävä kopioiva kerääjä. New space on jaettu kahteen yhtä suureen puolikkaaseen, joita kutsutaan puolitiloiksi: to-space ja from-space.
- Uudet objektit varataan to-spaceen osoitinta siirtämällä, mikä on erittäin nopeaa.
- Kun to-space täyttyy, suoritetaan minor GC eli scavenge-keräys: elossa olevat objektit kopioidaan from-spaceen, roolit vaihtuvat ja vanha to-space tyhjennetään kokonaan.
Koska kustannus on verrannollinen elossa säilyvien objektien määrään (ei roskan määrään) ja useimmat objektit ovat keräyshetkellä kuolleita, scavenge-keräykset ovat erittäin edullisia – tyypillisesti alle millisekunnin.
Varaaminen nuoressa sukupolvessa
Seuraa nuoren sukupolven toimintaa käytännössä. Alla oleva silmukka varaa miljoonia lyhytikäisiä objekteja. Jokainen niistä kuolee lähes välittömästi, joten ne eivät koskaan poistu new spacesta – Scavenger ottaa ne talteen tiheissä ja edullisissa sykleissä, ja heapUsed pysyy suunnilleen tasaisena.
Tämä on ihanteellinen varaamismalli pyyntökäsittelijälle: varaa vapaasti, anna objektien kuolla nopeasti ja anna GC:n tuskin huomata niitä.
// young_gen.js — millions of short-lived allocations
function work() {
let acc = 0;
for (let i = 0; i < 5_000_000; i++) {
// {x, y} is born and dies within the iteration
const point = { x: i, y: i * 2 };
acc += point.x + point.y;
}
return acc;
}
const before = process.memoryUsage().heapUsed;
const result = work();
const after = process.memoryUsage().heapUsed;
console.log('result:', result);
console.log('heapUsed delta (MB):', ((after - before) / 1024 / 1024).toFixed(2));Promootio: selviäminen old spaceen
Scavenge-keräyksestä selvinnyt objekti ei muutu heti vanhaksi. V8 seuraa kunkin objektin selviytymistä:
- Ensimmäisestä scavenge-keräyksestä selvinnyt objekti kopioidaan from-spaceen (se on edelleen nuori, mutta nyt välitilassa).
- Toisesta scavenge-keräyksestä selvinnyt objekti promotoidaan eli siirretään vanhaan sukupolveen.
Objektit, joihin viitataan riittävän pitkään, jotta ne selviävät kahdesta minor GC:stä, siirtyvät siis vanhaan sukupolveen. Käytössä on myös aggressiivinen promootio: jos to-space on scavenge-keräyksen jälkeen yli noin 80–90-prosenttisesti täynnä, V8 promotoi selvinneet objektit aikaisin välttääkseen pienen puolitilan jatkuvan kuormittamisen.
Promootion pakottaminen säilytetyllä viitteellä
Alla oleva malli säilyttää jokaisen varatun objektin pitkäikäisessä taulukossa. Objektit selviävät toistuvista scavenge-keräyksistä, ne promotoidaan old spaceen ja heapUsed kasvaa tasaisesti. Tämä näyttää täsmälleen rajoittamattomalta muistin sisäiseltä välimuistilta – ja näin hidas muistivuoto alkaa.
Opetus on tämä: old spacen täyttää säilyttäminen, ei varaamisnopeus. Miljoona objektia varaava mutta niistä yhtäkään säilyttämätön käsittelijä maksaa lähes olemattoman hinnan; tuhat objektia ikuisesti säilyttävä aiheuttaa vuodon.
// promotion.js — retained objects get tenured into old space
const retained = [];
for (let i = 0; i < 1_000_000; i++) {
// pushing keeps a live reference -> survives scavenges -> promoted
retained.push({ id: i, payload: 'item-' + i });
}
const u = process.memoryUsage();
console.log('retained length:', retained.length);
console.log('heapUsed (MB):', (u.heapUsed / 1024 / 1024).toFixed(2));
console.log('heapTotal (MB):', (u.heapTotal / 1024 / 1024).toFixed(2));Old space: merkitse–pyyhi–tiivistä
Old space kerätään major GC -keräyksellä, joka käyttää Mark-Sweep-Compact-menetelmää:
- Merkitse – käy objektigraafi läpi GC-juurista (globaali objekti, suorituspino ja natiivikahvat) ja merkitse kaikki saavutettavat objektit.
- Pyyhi – lisää merkitsemättömien eli kuolleiden objektien muisti vapaisiin listoihin uudelleenkäyttöä varten.
- Tiivistä – siirrä elossa olevia objekteja aika ajoin pirstoutumisen vähentämiseksi, jotta suuret varaukset löytävät yhtenäistä tilaa.
Major GC on paljon scavenge-keräystä kalliimpi, koska sen on käytävä koko old generation läpi. V8 välttää pitkiä taukoja suorittamalla suurimman osan merkitsemisestä samanaikaisesti taustasäikeissä ja asteittain JavaScript-suoritukseen lomitettuna. Tämä on Orinoco GC -arkkitehtuuri.
Saavutettavuus määrittää roskan
Objekti on roskaa täsmälleen silloin, kun se on saavuttamattomissa GC-juurista. V8 ei käytä viitelaskentaa, joten syklit kerätään oikein: kaksi toisiinsa viittaavaa objektia on edelleen roskaa, jos mikään saavutettava objekti ei viittaa kumpaankaan niistä.
Palvelinkoodin kannalta seuraus on tämä: muistivuoto on lähes aina vahingossa säilyvä viite, joka pitää objektin saavutettavana. Yleisiä syyllisiä ovat moduulitason Map-rakenteet, poistamatta jätetyt tapahtumankuuntelijat ja suuria skooppeja kaappaavat sulkeumat.
// reachability.js — an unintentional cycle is still collectable
function makePair() {
const a = {};
const b = {};
a.peer = b; // a -> b
b.peer = a; // b -> a (cycle)
return a;
}
let root = makePair();
console.log('reachable via root:', root.peer.peer === root);
root = null; // drop the only root reference
// Both a and b are now unreachable despite the cycle;
// V8 will reclaim them on the next GC. No leak.
console.log('root dropped; cycle is now garbage');Kekomuistin mitoittaminen: --max-old-space-size
Vanhalle sukupolvelle voi määrittää ylärajan. 64-bittisessä Nodessa old spacen oletusraja on noin 2 Gt (vanhemmissa versioissa oletus oli lähellä 1,5:tä gigatavua; moderni Node suhteuttaa rajan käytettävissä olevaan muistiin). Kun old space ei voi enää kasvaa ja major GC ei vapauta riittävästi muistia, prosessi päättyy virheeseen FATAL ERROR: ... JavaScript heap out of memory.
Voit nostaa ylärajaa V8:n lipulla --max-old-space-size (megatavuina):
node --max-old-space-size=4096 server.jsasettaa old spacen rajaksi 4 Gt.
Rajan nostaminen antaa lisää liikkumavaraa, mutta ei korjaa vuotoa – vuoto saavuttaa myöhemmin vain uuden, korkeamman rajan. Yhdistä mitoitukseen aina vuodon selvittäminen.
GC:n tarkkailu perf_hooks-moduulilla
Voit tarkkailla minor- ja major GC -tapahtumia suoraan sisäänrakennetun perf_hooks-moduulin PerformanceObserver-toteutuksella käyttämällä arvoa entryTypes: ['gc']. Kunkin merkinnän detail.kind kertoo keräyksen tyypin:
NODE_PERFORMANCE_GC_MINOR– scavenge-keräys (nuori sukupolvi).NODE_PERFORMANCE_GC_MAJOR– mark-sweep-compact-keräys (vanha sukupolvi).
Käytä tätä kuormitustesteissä selvittääksesi, hallitsevatko palvelua edulliset minor GC -keräykset (terve tilanne) vai usein toistuvat kalliit major GC -keräykset (säilymisongelma).
// gc_observer.js — log each GC event and its duration
const { PerformanceObserver, constants } = require('perf_hooks');
const kinds = {
[constants.NODE_PERFORMANCE_GC_MINOR]: 'minor (scavenge)',
[constants.NODE_PERFORMANCE_GC_MAJOR]: 'major (mark-sweep)',
[constants.NODE_PERFORMANCE_GC_INCREMENTAL]: 'incremental',
[constants.NODE_PERFORMANCE_GC_WEAKCB]: 'weak-callback',
};
const obs = new PerformanceObserver((list) => {
for (const e of list.getEntries()) {
console.log(kinds[e.detail.kind] || 'other', '-', e.duration.toFixed(2), 'ms');
}
});
obs.observe({ entryTypes: ['gc'] });
// generate garbage to trigger collections
let sink = [];
for (let i = 0; i < 2_000_000; i++) {
sink.push({ i });
if (sink.length > 50_000) sink = [];
}
console.log('done allocating');Suunnittelu sukupolvien GC:tä varten
Käytännön toimintatapoja, jotka pitävät palvelun V8:n sukupolvikerääjälle sopivana:
- Anna pyyntötietojen kuolla nuorina. Vältä pyyntökohtaisten objektien tallentamista moduulitason rakenteisiin; ne promotoidaan ja aiheuttavat major GC -kustannuksia jatkuvasti.
- Rajaa välimuistien koko. Käytä enimmäiskoolla varustettua LRU-välimuistia rajoittamattoman
Map-rakenteen sijaan, jotta old space pysyy tasaisena. - Suosi
WeakMap- jaWeakRef-rakenteita assosiatiivisille metatiedoille, joiden avaimina ovat omistamiesi objektien sijaan muut objektit – merkinnät katoavat, kun avain muuttuu saavuttamattomaksi. - Käytä suuria puskureita uudelleen poolien avulla sen sijaan, että varaisit niitä uudelleen; suuret varaukset kuormittavat old spacea ja tiivistämistä.
Ajatusmalli on tämä: edullinen ja lyhytikäinen nuoren sukupolven vaihtuvuus on normaalia; vanhan spacen pitkäikäinen kasvu on se, jota on tarkkailtava.
// weakmap_metadata.js — metadata that auto-collects with its key
const lastSeen = new WeakMap();
function touch(session) {
lastSeen.set(session, Date.now()); // no strong ref to session
return lastSeen.get(session);
}
let session = { id: 'abc' };
console.log('touched at:', touch(session));
session = null; // session unreachable -> WeakMap entry eligible for GC
console.log('session released; WeakMap entry will be reclaimed');Pikatarkistus: objektin elinikä
Pohdintatehtävä siitä, miten objekti kulkee V8:n sukupolvien läpi.
Kertaus: kekomuisti, sukupolvet ja eliniät
Keskeiset asiat:
- Kaksi sukupolvea. Uusi tila (young) sisältää juuri varatut oliot, vanha tila (old) eloonjääneet oliot.
- Heikko sukupolvihypoteesi. Useimmat oliot kuolevat nuorina, joten V8 kerää uuden tilan usein ja edullisesti, mutta vanhan tilan harvoin ja perusteellisesti.
- Scavenge. Minor GC kopioi elävät oliot puolialueiden välillä; kustannus määräytyy eloonjääneiden, ei roskan, määrän mukaan.
- Promotion. Kahdesta scavengesta selviäminen (tai to-tilan täyttyminen) siirtää olion vanhaan tilaan.
- Mark-Sweep-Compact. Major GC käy juurista lähtien oliot läpi, vapauttaa saavuttamattomat oliot ja tiivistää muistia ajoittain — toiminta suoritetaan pääosin samanaikaisesti ja asteittain.
- Vuodot ovat säilyttämistä. Saavuttamattomat oliot voidaan kerätä, myös syklien kautta; vanhan tilan täyttävät ja OOM-tilanteen aiheuttavat tahattomat pitkäikäiset viittaukset.
- Työkalut.
process.memoryUsage(),perf_hooks-moduulin GC-merkinnät ja--max-old-space-sizeauttavat mittaamaan, seuraamaan ja mitoittamaan kekoa.
Opi JavaScript tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 22
- Oppitunnit
- 92
Usein kysytyt kysymykset
Onko oppitunti ”V8:n heap, generatiivinen GC ja olioiden elinkaaret” ilmainen?
Kyllä – oppitunnin ”V8:n heap, generatiivinen GC ja olioiden elinkaaret” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Node.js-taustakehityksen bootcamp-kurssin, päivitä CoddyKit PROhon. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”V8:n heap, generatiivinen GC ja olioiden elinkaaret”?
Ymmärrä, miten V8 varaa ja kerää muistia nuoren ja vanhan sukupolven alueilla. Harjoittelet Node.js-taustakehityksen bootcamp-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Node.js-taustakehityksen bootcamp-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Node.js-taustakehityksen bootcamp-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 1/4.
Kuinka kauan ”V8:n heap, generatiivinen GC ja olioiden elinkaaret”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Node.js-taustakehityksen bootcamp-oppitunnilla?
Kyllä. Jokainen Node.js-taustakehityksen bootcamp-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- V8:n heap, generatiivinen GC ja olioiden elinkaaret
- Heap-snapshotien kaappaaminen ja vertaileminen
- Suorittimen profilointi ja flame graphit kuumille suorituspoluille
- Yleisten muistivuotokuvioiden tunnistaminen ja korjaaminen