Veröffentlicht: 29. Oktober 2026
Google Chat ist eine große Single-Page-Anwendung. Nutzer lassen die Web-App regelmäßig tagelang oder wochenlang in ihrem Browsertab geöffnet. In langlebigen Anwendungen ist der Zustand des clientseitigen Speichers ein wichtiges Anliegen. Nicht erfasste Closures und getrennte DOM-Bäume bauen sich langsam auf und beeinträchtigen schließlich die UI-Leistung, die Pausen für die automatische Speicherbereinigung oder die Tab-Abstürze auf eingeschränkten Geräten.
Um dieses Problem zu beheben, hat das Google Chat-Team eine Initiative gestartet, um Probleme mit dem Arbeitsspeicher zu finden und zu beheben. Sie haben die Chrome-Entwicklertools für KI-Agenten verwendet, die den MCP-Server der Entwicklertools enthalten, um einen schwierigen manuellen Prozess in einen automatisierten Workflow umzuwandeln.
Die Herausforderung: Eine Nadel im 500-MB-Heuhaufen
Um genau herauszufinden, warum ein geleaktes Objekt im Arbeitsspeicher verbleibt, müssen die Aufbewahrungspfade durch stark minimierten Code verfolgt werden. Die jüngsten Verbesserungen am Speicherbereich der Chrome-Entwicklertools (z. B. erweiterte Kantenfilterung und Objektabfragen) machen dies zwar einfacher, aber die manuelle Durchführung bei einem 500-MB-Snapshot kann immer noch langsam und mühsam sein.
Und um ein Leck zu beheben, sind zwei verschiedene Arten von Wissen erforderlich:
- Sie müssen verstehen, wie die V8-JavaScript-Engine kompilierten Bytecode verarbeitet.
- Außerdem müssen Sie die spezifische Statusverwaltung und den Komponentenlebenszyklus Ihrer Anwendung kennen.
Die manuelle Verknüpfung dieser beiden unterschiedlichen Wissensbereiche erfordert in der Regel tagelange Untersuchungen und die Zusammenarbeit mit mehreren Entwicklern pro Datenleck.
Die Lösung: Heap mit einem KI-Agenten durchsuchen
Das Team erkannte, dass Large Language Models zwar sehr gut darin sind, strukturierte Daten zu analysieren, aber die richtigen Tools benötigen, um riesige Dateien zu verarbeiten und gleichzeitig den Tokenverbrauch im Rahmen zu halten. Durch die Verbindung von Google Antigravity mit dem Open-Source-Chrome-Entwicklertools-MCP-Server mit aktivierten Speichertools (--memoryDebugging=true) erhielt der Agent direkten programmatischen Zugriff auf den V8-Heap.
Anstatt dass ein Entwickler die Retainer-Bäume im Chrome-Entwicklertools-Speicherbereich manuell maximiert, hat der Agent den Snapshot direkt abgefragt. Das Team hat den Prozess mit einem einfachen Prompt gestartet:
Discover memory leaks in the heap snapshot chat.heapsnapshot using Chrome DevTools memory tools.
Mithilfe des Memory Debugging Skill hat der Agent einen strukturierten Workflow mit bestimmten MCP-Tools ausgeführt:
- Snapshot erfassen oder laden: Verwenden Sie Tools wie
take_heapsnapshotoder lesen Sie eine vorhandene Datei. - Heap-Struktur analysieren: Es wurde mit
get_heapsnapshot_detailsundcompare_heapsnapshotsnach gängigen Leak-Quellen gesucht. - Trace-Beibehaltungspfade: Es wurde
get_heapsnapshot_retaining_pathsverwendet, um den Dominatorbaum zurück zu den Garbage-Collection-Roots zu durchlaufen. - Korrekturen generieren: Die Retaining Paths wurden mit dem Quellcode der Anwendung abgeglichen, um sehr spezifische Patches auszugeben.
Technische Informationen: Drei häufige Muster für Speicherlecks
Der KI-Agent hat über 30 verschiedene Lecks identifiziert. Die folgenden drei Muster sind die wirkungsvollsten, die der Agent gefunden hat. Sie stellen die größten Speichereinsparungen und die häufigsten Architektur-Anti-Patterns in modernen Webanwendungen dar.
Der statische Fehler-Stacktrace-Trap
Der Agent hat festgestellt, dass das größte einzelne Leck mit vorab zugewiesenen Fehlerobjekten zusammenhängt, die dauerhaft fast 20 MB Arbeitsspeicher belegten. Beispielsweise wurden durch eine einzelne statische Fehlerkonstante (EMPTY_KEY_ERROR) 14, 1 MB beibehalten.
Das Problem: In modernen JavaScript-Engines wie V8 wird beim Erstellen eines Error-Objekts ein Snapshot des aktuellen Aufrufstacks erstellt, um das Debugging zu erleichtern. Die enorme beibehaltene Größe ist auf genau diesen Mechanismus zurückzuführen:
- Der Fehler war eine globale Konstante: Er wurde während der ersten Modulbewertung instanziiert.
- Die JavaScript-Engine hat den Stack erfasst: Die aktive Funktion auf dem Stack zu diesem Zeitpunkt war der Hauptinitialisierungs-Wrapper der Anwendung (ein Immediately Invoked Function Expression oder IIFE).
- Das Array hat den Speicher blockiert: Da die Anwendung den Fehler nie bereinigt hat, enthielt das interne Stacktrace-Array einen permanenten Verweis auf die Initialisierungsfunktion des Haupt-Bundles.
// Ineffective pattern: Instantiating Error objects at module evaluation time
export const EMPTY_KEY_ERROR = new Error("Key cannot be empty");
export function validateKey(key: string) {
if (!key) {
throw EMPTY_KEY_ERROR;
}
}
Die Lösung: Das Team hat den Code so geändert, dass Fehler dynamisch erstellt werden, wenn sie auftreten.
Wenn ein wiederverwendbarer statischer Fehler unbedingt erforderlich ist, werden durch das explizite Löschen des Stacks (EMPTY_KEY_ERROR.stack = '';) die erfassten internen V8-Frames sofort freigegeben.
// Recommended: Construct errors dynamically
export function validateKey(key: string) {
if (!key) {
throw new Error("Key cannot be empty");
}
}
// Or clear the stack property if a static instance is necessary:
export const REUSABLE_ERROR = new Error("Static error");
REUSABLE_ERROR.stack = ""; // Releases the captured initialization frame
Skalierung globaler Fenster-Event-Listener
Der Agent hat ein Problem mit der Speicherskalierung im Zusammenhang mit visibilitychange-Ereignis-Listenern gefunden. Dies ist zwar kein striktes „Leck“ (da sie schließlich bereinigt werden, wenn die Komponente entfernt wird), aber es erhöhte die Speichernutzung, da es für jede Nachricht auf dem Bildschirm einen Listener gab. Der Snapshot ergab 1.142 integrierte Event-Listener, wobei 128 aktive Listener direkt an das globale Fensterobjekt angehängt waren.
Das Problem: In JavaScript sind Event-Listener-Callbacks Closures. Ein Closure behält Verweise auf den umschließenden lexikalischen Bereich bei, damit die benötigten Variablen zugänglich bleiben. Der Agent hat 126 dieser Listener auf eine einzelne Komponente zurückgeführt: RelativeTimeController. Jeder Zeitstempel in der Chat-Benutzeroberfläche (z. B. „vor 5 Minuten“) hatte einen eigenen Controller, der einen privaten Sichtbarkeits-Listener registrierte, um die Zeit zu aktualisieren. Da jeder Listener seine Controller-Instanz an der Fensterwurzel verankerte, band ein dichtes Netz aktiver Closures während der Lebensdauer des sichtbaren Chat-Threads erheblichen Speicherplatz.
// Ineffective pattern: Attaching global window listeners in individual UI controllers
export class RelativeTimeController {
constructor() {
this.visibilityListener = () => this.updateTime();
window.addEventListener("visibilitychange", this.visibilityListener);
}
// If dispose() fails to run or unregister, the closure pins the controller
}
Die Lösung: Das Team hat globale Fensterereignisse zentralisiert. Eine freigegebene TimerService registriert jetzt genau einen Sichtbarkeits-Listener im Fenster und sendet Aktualisierungen an alle aktiven Zeitstempel-Controller. Dadurch wurden 126 Listener sofort auf genau einen reduziert.
// Recommended: Centralized event coordination using a shared service
export class VisibilityService {
private listeners = new Set<() => void>();
constructor() {
// Single global listener throttled for the entire application
window.addEventListener("visibilitychange", () => this.notifyAll());
}
subscribe(callback: () => void): () => void {
this.listeners.add(callback);
return () => this.listeners.delete(callback); // Unsubscribe handle
}
}
Drittanbieter-SDK und eingebettete iFrame-Retainer
Getrennte DOM-Knoten sind Elemente, die aus dem Dokumentbaum entfernt wurden, aber weiterhin von einer JavaScript-Variablen referenziert werden. Wenn ein einzelnes Element in einem getrennten DOM-Baum von einer Stammvariablen aufbewahrt wird, kann der gesamte getrennte Baum nicht freigegeben werden.
Das Problem: Wenn Nutzer zwischen Chatbereichen wechselten, stellte der Agent fest, dass ganze losgelöste DOM-Bäume im Arbeitsspeicher verblieben. Der Aufbewahrungspfad wurde zu einem eingebetteten Mediaplayer in der Benutzeroberfläche des Chatbereichs zurückverfolgt. Wenn ein Chatbereich deaktiviert wurde, wurde die UI-Komponente von der Seite entfernt, das zugrunde liegende Media Player SDK wurde jedoch nie explizit gelöscht. Die Drittanbieterbibliothek verwaltete ein internes globales Verzeichnis aktiver iFrame-IDs, wodurch das getrennte DOM aktiv blieb.
Lösung: Alle eingebetteten iFrames und Drittanbieter-Widgets müssen in den Lebenszyklus der Komponentenentsorgung eingebunden werden. Wenn Sie die Methode .destroy() explizit für den Player aufrufen, wird der Eintrag aus der globalen SDK-Registry gelöscht und das DOM kann bereinigt werden.
// Recommended: Explicit teardown of embedded frames and SDK instances
export class MediaPlayerController {
private player: ThirdPartyPlayer | null = null;
dispose(): void {
if (this.player) {
this.player.destroy(); // Clears entry from global SDK registry
this.player = null;
}
}
}
Zusätzliche Muster
Die drei zuvor genannten Muster waren zwar die wirkungsvollsten im Hinblick auf die eingesparten Megabyte, der Agent hat jedoch auch über 27 andere Lecks erkannt und behoben. Dazu gehörten:
- Bildarray-Telemetrie: Hier wurden getrennte DOM-Bäume durch benutzerdefinierte Diagnosearrays am Leben erhalten, die Messwerte für Bilder im sichtbaren Bereich erfassen.
- Fehler im Lebenszyklus von Framework-Komponenten: Benutzerdefinierte Drop-down-Menüs, die in den Dokumenttext verschoben wurden, umgingen das Unmounting ihrer ursprünglichen übergeordneten Elemente.
- Implizite Argumentobjekt-Closure-Falle: Hier haben innere Closures unbeabsichtigt das gesamte Argumentarray äußerer Funktionen erfasst.
Auswirkung: Proaktive Speicherverwaltung als Workflow
Durch die Ausstattung eines KI-Agenten für die Programmierung mit den Chrome-Entwicklertools für KI-Agenten konnte das Google Chat-Team eine hochspezialisierte Aufgabe automatisieren. Die Verfolgung eines komplexen Speicherlecks wurde von Tagen manueller Heap-Durchsuchung auf Minuten automatisierter Analyse reduziert.
Ähnlich wie P2ER die „obligatorische empirische Überprüfung“ zur Durchsetzung der UI-Stabilität implementiert hat, können Teams diesen Ansatz zur Speicherprüfung als proaktive Schutzmaßnahme übernehmen. Entwickler ignorieren Speicherprobleme oft, weil sie davon ausgehen, dass sie nur in sehr großen oder komplexen Anwendungen auftreten. Wie diese Fallstudie zeigt, können sich jedoch gängige Muster wie Standard-Event-Listener oder statische Fehler unbemerkt ansammeln. Wenn Sie Tools wie die Chrome-Entwicklertools für KI-Agenten in Ihren täglichen Entwicklungszyklus einbinden, wird die KI von einem einfachen Codegenerator zu einem zuverlässigen Qualitäts-Gate, das unbemerkte Regressionen abfängt, bevor sie in die Produktion gelangen.