Caches der Quellkarten von Node stellt im Entwicklungsmodus einen stillen Speicherverlust dar.
Erfahren Sie, warum das Aktivieren von --enable-source-maps oder NODE_V8_COVERAGE aufgrund wiederholter eval-Aufrufe zu einem unbegrenzten Wachstum des Heap-Speichers führen kann, und wie Sie dies heute diagnostizieren und abmildern können.
Der Node-Prozess scheint unmittelbar nach dem Start völlig gesund zu sein, nimmt aber beim weiteren Bearbeiten des Codes allmählich an Speicherbedarf zu. Wenn dieser Prozess mit --enable-source-maps (oder mit gesetztem NODE_V8_COVERAGE) gestartet wurde und etwas in Ihrem Codestack jedes Mal eval mit einer neuen //# sourceURL-Angabe aufruft, haben Sie wahrscheinlich den Übeltäter gefunden: ein stark gefüllter Source-Map-Cache, der generierte Einträge ansammelt und sie niemals freigibt. Der Heap wächst weiter. Sie starten den Prozess neu – er wächst erneut.
Sie versuchen, die Müllsammlung zu zwingen. Sie löschen alle Referenzen, an die Sie denken können. Dennoch steigt der Heap weiter an.
Das Wachstumsmuster
Stellen Sie sich einen Entwicklungsserver vor, der eine Weile weiterläuft, oder generell jeden lang laufenden Node-Prozess, der synthetische Stack-Traces oder „Owner Frames“ erzeugt, die mit eindeutigen Quell-URLs versehen sind. Sowohl RSS als auch heapUsed steigen die ganze Zeit an. Das Neustarten des Prozesses setzt den Verlauf neu, doch das Ausführen derselben Arbeitslast ohne die source-map-Flag lässt den Speicherverbrauch konstant.
In der öffentlichen Node.js-Issue-Tracking-Seite zu diesem Verhalten (nodejs/node#65760) wird eine minimale Replikation durchgeführt, bei der etwa 90 Bytes Quellcode gegenüber einer einzigen externen Source Map bewertet werden, wobei bei jeder Iteration nur die sourceURL geändert wird. Nach Durchführung der erzwungenen Garbage Collection:
Evals | with --enable-source-maps | no flag
0 | 5 MB | 5 MB
400 | 170 MB | 5 MB
800 | 334 MB | 6 MB
1200 | 499 MB | 6 MB
Das ergibt in etwa 415 KB, die pro eval-Aufruf behalten bleiben, wobei kein Obergrenzen sichtbar sind.
Falls Sie mit dem Next.js App Router arbeiten, zeigt sich dies oft darin, dass next dev bei jeder Dateiänderung um Dutzende Megabyte an Größe zunimmt. Das Owner-Stack-Mechanismus der React Server Components führt eval einmal pro Stack-Frame aus und kennzeichnet jeden Aufruf mit etwas wie //# sourceURL=about://React/…?<counter++> zusammen mit einer großen, eingebetteten Quellkartei. Da dieser Zähler bei jedem Aufruf inkrementiert wird, ist jeder Cache-Schlüssel einzigartig – daher wird nichts wiederverwendet. Der entsprechende Diskussionsthread in Next.js ist vercel/next.js#98221 – betrachten Sie ihn als den Ort, an dem das Problem auftritt, nicht als eine unabhängige Ursache.
Warum der Cache die Daten nicht freigibt
Die Flagge --enable-source-maps weist Node an, Source Maps zu cachen, damit Laufzeit-Stacktraces in die ursprünglichen Quelldateien übersetzt werden können (siehe die CLI-Dokumentation). Reguläre Modulquellen werden über einen schwach gesperrten Cache abgelegt, sodass der Garbage Collector sie zurückgewinnen kann, sobald sie nicht mehr referenziert werden. Generierte Quellen hingegen – jene, die durch den isGeneratedSource-Branch verarbeitet werden – landen in generatedSourceMapCache, einem einfachen, stark referenzierten Map, der in lib/internal/source_map/source_map_cache.js definiert ist und niemals bereinigt wird.
Der Kommentar im Code bezüglich dieses Caches geht davon aus, dass es während der Lebensdauer eines Prozesses nur wenige generierte Quellen geben wird. Hot Module Replacement sowie die Regeneration des Owner-Stacks durchbrechen diese Annahme völlig. Jeder eindeutige sourceURL wird zu einer dauerhaften Schlüsselwerte, alte Einträge werden niemals gelöscht, und die vollständig analysierte Karte für jeden Eintrag bleibt im Speicher gespeichert.
Durch die Einstellung von NODE_V8_COVERAGE wird genau derselbe Cache-Pfad genutzt, wodurch Sie ebenfalls ein unbegrenztes Wachstum beobachten werden, solange sich Ihre generierten eval-Schlüssel ständig ändern.
Es gibt bereits einen offenen Pull Request upstream (#65761), der den Cache für generated-sources mit einer LRU-Strategie basierend auf einem Byte-Budget begrenzt – in der neuesten Revision auf 32 MiB – und die Einträge bei jeder Lektüre aktualisiert, damit Karten für weiterhin aktive generierte Funktionen nicht vorzeitig entfernt werden. Derzeit bleibt dieser PR offen und ist mit needs-ci markiert. Er wurde noch nicht eingemergt und ist auch nicht Teil einer veröffentlichten Node-Build-Version, daher sollten Sie nicht annehmen, dass Ihre aktuelle LTS-Version bereits diesen Fix enthält.
Diagnose bestätigen
- Überprüfen Sie, ob Ihr langlaufender Prozess mit
--enable-source-mapsgestartet wurde oder obNODE_V8_COVERAGEgesetzt ist, und ob etwas immer wieder Code mit einer neuen//# sourceURLausführt – dies könnte HMR, RSC-Owner-Stacks oder benutzerdefinierte Codegenerierung sein.
process.memoryUsage().heapUsed nach Ausführung einer Schleife mit erzwungenem GC auf, entweder in einer eigenständigen Wiederholung unter Verwendung von node --expose-gc oder durch Erstellung eines Heap-Snapsshots über den Inspektor im laufenden Prozess.generatedSourceMapCache oder context:generatedSourceMapCache suchen. Die Berichterstatter des Problems fanden während einer echten next dev-Sitzung Tausende von gespeicherten Einträgen, die insgesamt mehr als einen Gigabyte an sourcesContent- und mappings-Daten enthielten.Die Erhöhung von --max-old-space-size ist keine Lösung – sie verschiebt lediglich den unvermeidlichen Absturz aufgrund von Speicherengpässen.
Was Sie jetzt tun sollten
Wählen Sie den Ansatz, der zu Ihrer Konfiguration passt:
- Für Next.js-Projekte: Handeln Sie umgehend: Führen Sie
next dev --disable-source-mapsaus. Berichterstatter im Thread #65760 stellten fest, dass der Speicherverbrauch deutlich abnahm – etwa +6 MB pro Änderung im Vergleich zu rund +89 MB bei aktivierten Quellkarten, basierend auf ihren Messungen. Die Lesbarkeit der Stack-Traces nimmt zwar etwas ab, doch Ihr Rechner läuft nicht mehr aus dem Speicher heraus. - Für andere langlaufende Node-Tools: Entfernen Sie
--enable-source-mapsoder deaktivieren SieNODE_V8_COVERAGEauf jedem Hot-Reloading-Server, solange tatsächlich mappierte Stack-Traces erforderlich sind. Verwenden Sie Quellkarten stattdessen für kurze Debugging-Sitzungen.
Fazit
Ihr Entwicklungsserver verbraucht nicht willkürlich Speicher ohne Grund. Die Kombination von --enable-source-maps mit wiederholten Auswertungen, die eindeutige sourceURL-Werte verwenden, füllt einen stark referenzierten generatedSourceMapCache, der seine Einträge niemals freigibt. Reguläre Modul-Quellkarten können gecacht und später gelöscht werden; generierte Quellkarten hingegen nicht, zumindest bis #65761 in einer Version veröffentlicht wird. Schalten Sie Quellkarten bei Hot-Reload-Prozessen vorerst aus und planen Sie einen Upgrade, sobald der begrenzte Cache verfügbar ist.
Sollten Sie also das nächste Mal feststellen, dass der RSS-Wert beim einfachen Bearbeiten von Dateien ansteigt und eine erzwungene Garbage Collection nichts dagegen ausrichten kann, prüfen Sie, ob dieser Flag der Grund dafür ist.
Verwandte Artikel
- TypeScript’s Go Compiler und native Ausführung: Ein Migrationsleitfaden — Erfahren Sie, wie TypeScript’s auf Go basierender Compiler sowie die native Ausführung in Node.js die Codebasen von React und Next.js beeinflussen und was Sie jetzt in Ihrer tsconfig korrigieren sollten.
- Jest durch Node’s nativen Testlaufzeitmechanismus in Node 24 ersetzen — Eine Praxisbeispiel-Migration zeigt, wie Node 24s integrierter Testlaufzeitmechanismus sowie die native TypeScript-Unterstützung die CI-Zeiten verkürzen und gleichzeitig vier Abhängigkeiten beseitigen.