Jak Google Chat rozwiązał ponad 30 problemów z pamięcią za pomocą Narzędzi deweloperskich w Chrome dla agentów

Vatan Aggarwal
Vatan Aggarwal Software Engineer

Opublikowano: 29 października 2026 r.

Google Chat to duża aplikacja jednostronicowa. Użytkownicy regularnie pozostawiają aplikację internetową otwartą na karcie przeglądarki przez kilka dni lub tygodni. W przypadku aplikacji o długim czasie działania stan pamięci po stronie klienta jest kluczowy. Niezebrane zamknięcia i odłączone drzewa DOM powoli się gromadzą, co ostatecznie wpływa na wydajność interfejsu, przerwy w zbieraniu nieużytków lub awarie kart na urządzeniach o ograniczonych zasobach.

Aby rozwiązać ten problem, zespół Google Chat uruchomił inicjatywę mającą na celu wykrywanie i rozwiązywanie problemów z pamięcią. Użyli Narzędzi deweloperskich w Chrome dla agentów, które obejmują serwer MCP Narzędzi deweloperskich, aby przekształcić trudny proces ręczny w zautomatyzowany przepływ pracy.

Wyzwanie: igła w stogu siana o rozmiarze 500 MB

Aby ustalić, dlaczego obiekt, który wyciekł, jest przechowywany w pamięci, trzeba prześledzić ścieżki przechowywania w mocno zminimalizowanym kodzie. Chociaż ostatnie ulepszenia panelu Pamięć w Narzędziach deweloperskich w Chrome (np. zaawansowane filtrowanie krawędzi i zapytania o obiekty) ułatwiają to zadanie, ręczne wykonywanie tej czynności na zrzucie o rozmiarze 500 MB może być powolne i męczące.

Naprawa wycieku wymaga 2 odrębnych zestawów wiedzy:

  • Musisz wiedzieć, jak silnik JavaScriptu V8 obsługuje skompilowany kod bajtowy.
  • Musisz też zrozumieć, jak działa zarządzanie stanem i cykl życia komponentów w Twojej aplikacji.

Ręczne połączenie tych 2 różnych zestawów informacji zwykle wymaga kilku dni analiz i współpracy z wieloma deweloperami w przypadku każdego wycieku.

Rozwiązanie: poruszanie się po stercie za pomocą agenta

Zespół zdał sobie sprawę, że chociaż duże modele językowe są bardzo dobre w rozumowaniu na podstawie uporządkowanych danych, potrzebują odpowiednich narzędzi do przetwarzania ogromnych plików przy zachowaniu rozsądnego zużycia tokenów. Po połączeniu Google Antigravity z serwerem MCP Narzędzi deweloperskich w Chrome typu open source z włączonymi narzędziami pamięci (--memoryDebugging=true) agenci uzyskali bezpośredni dostęp programowy do sterty V8.

Zamiast ręcznego rozwijania drzew obiektów utrzymujących w panelu pamięci Narzędzi deweloperskich w Chrome agent wysłał zapytanie bezpośrednio do zrzutu. Zespół zaczął od prostego prompta:

Discover memory leaks in the heap snapshot chat.heapsnapshot using Chrome DevTools memory tools.

Za pomocą umiejętności debugowania pamięci agent wykonał ustrukturyzowany przepływ pracy przy użyciu konkretnych narzędzi MCP:

  1. Zrób lub wczytaj zrzut: użyj narzędzi takich jak take_heapsnapshot lub odczytaj istniejący plik.
  2. Analiza struktury sterty: przeskanowano pod kątem typowych źródeł wycieków za pomocą get_heapsnapshot_details i compare_heapsnapshots.
  3. Śledzenie ścieżek zachowywania: użyto get_heapsnapshot_retaining_paths, aby przejść drzewo dominatorów z powrotem do korzeni odzyskiwania pamięci.
  4. Generowanie poprawek: narzędzie porównało ścieżki utrzymywania z kodem źródłowym aplikacji, aby wygenerować bardzo konkretne poprawki.

Informacje techniczne: 3 typowe wzorce wycieków pamięci

Agent zidentyfikował ponad 30 różnych wycieków. Oto 3 najważniejsze wzorce znalezione przez agenta, które reprezentują największe oszczędności pamięci i najczęstsze antywzorce architektoniczne w nowoczesnych aplikacjach internetowych.

Pułapka zrzutu stosu błędów statycznych

Agent wykrył, że największy pojedynczy wyciek był związany z wstępnie przydzielonymi obiektami błędów, które trwale zajmowały prawie 20 MB pamięci. Na przykład pojedyncza statyczna stała błędu (EMPTY_KEY_ERROR) zajmowała 14, 1 MB.

Problem: w nowoczesnych silnikach JavaScript, takich jak V8, utworzenie obiektu Error powoduje przechwycenie zrzutu bieżącego stosu wywołań, co ułatwia debugowanie. Ogromny rozmiar zwolnionego miejsca wynika z tego mechanizmu:

  • Błąd był stałą globalną: został utworzony podczas początkowej oceny modułu.
  • Silnik JavaScriptu przechwycił stos: aktywną funkcją na stosie w tym czasie był główny kod inicjujący aplikacji (natychmiast wywoływane wyrażenie funkcyjne, IIFE).
  • Tablica zablokowała pamięć: ponieważ aplikacja nigdy nie usunęła błędu, jej wewnętrzna tablica zrzutu stosu zawierała stałe odwołanie do funkcji inicjowania głównego pakietu.
// 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;
  }
}

Rozwiązanie: zespół zmienił kod, aby błędy były tworzone dynamicznie w momencie ich wystąpienia. Jeśli błąd statyczny wielokrotnego użytku jest bezwzględnie konieczny, jawne wyczyszczenie stosu (EMPTY_KEY_ERROR.stack = '';) natychmiast zwalnia przechwycone ramki wewnętrzne V8.

// 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

Skalowanie detektorów zdarzeń okna globalnego

Agent wykrył problem ze skalowaniem pamięci związany z detektorami zdarzeń visibilitychange. Chociaż nie jest to ścisły „wyciek” (ponieważ są one ostatecznie czyszczone po odłączeniu komponentu), zwiększyło to wykorzystanie pamięci, ponieważ dla każdej wiadomości na ekranie był jeden detektor. Zrzut ujawnił 1142 wbudowane detektory zdarzeń, z czego 128 aktywnych detektorów było dołączonych bezpośrednio do globalnego obiektu okna.

Problem: w języku JavaScript wywołania zwrotne detektora zdarzeń są domknięciami. Domknięcie zachowuje odwołania do otaczającego je zakresu leksykalnego, aby zapewnić dostępność potrzebnych zmiennych. Agent ustalił, że 126 z tych detektorów jest powiązanych z jednym komponentem: RelativeTimeController. Każda sygnatura czasowa w interfejsie Google Chat (np. „5 minut temu”) zarządzała własnym kontrolerem, który rejestrował prywatny detektor widoczności, aby aktualizować czas. Ponieważ każdy detektor zakotwiczał swoją instancję kontrolera w głównym oknie, gęsta sieć aktywnych zamknięć zajmowała znaczną ilość pamięci przez cały czas wyświetlania wątku czatu.

// 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
}

Rozwiązanie: zespół scentralizował globalne zdarzenia okna. Udostępniony obiekt TimerService rejestruje teraz dokładnie 1 detektor widoczności w oknie i rozsyła aktualizacje do wszystkich aktywnych kontrolerów sygnatur czasowych. W ten sposób 126 odbiorców zostało natychmiast zredukowanych do 1.

// 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
  }
}

Zewnętrzny pakiet SDK i umieszczone elementy iframe

Odłączone węzły DOM to elementy, które zostały usunięte z drzewa dokumentu, ale nadal są przywoływane przez zmienną JavaScriptu. Jeśli pojedynczy element w odłączonym drzewie DOM jest zachowywany przez zmienną główną, nie można odzyskać całego odłączonego drzewa.

Problem: gdy użytkownicy przechodzili między pokojami czatu, agent zauważył, że całe odłączone drzewa DOM pozostawały w pamięci. Ścieżka przechowywania prowadziła z powrotem do odtwarzacza multimedialnego umieszczonego w interfejsie pokoju czatu. Gdy pokój czatu został odłączony, komponent interfejsu został usunięty ze strony, ale pakiet SDK odtwarzacza multimediów nie został jawnie zniszczony. Biblioteka zewnętrzna utrzymywała wewnętrzny globalny słownik aktywnych identyfikatorów elementów iframe, dzięki czemu odłączony DOM pozostawał aktywny.

Rozwiązanie: upewnij się, że wszystkie umieszczone elementy iframe i widżety innych firm są powiązane z cyklami życia komponentów. Wywołanie metody .destroy() w odtwarzaczu powoduje usunięcie wpisu z globalnego rejestru pakietu SDK i umożliwia usunięcie DOM przez moduł odśmiecania pamięci.

// 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;
    }
  }
}

Dodatkowe wzorce

Chociaż 3 wspomniane wcześniej wzorce były najbardziej skuteczne pod względem zaoszczędzonych megabajtów, agent wykrył i naprawił też ponad 27 innych wycieków. Oto niektóre z nich:

  • Telemetria tablicy obrazów: odłączone drzewa DOM były utrzymywane przy życiu przez niestandardowe tablice diagnostyczne śledzące dane dotyczące obrazów w części strony widocznej na ekranie.
  • Błędy cyklu życia komponentów platformy: niestandardowe menu rozwijane przeniesione do treści dokumentu omijały odmontowywanie ich pierwotnych elementów nadrzędnych.
  • Pułapka domknięcia obiektu argumentów niejawnych: gdy domknięcia wewnętrzne nieumyślnie przechwytywały całą tablicę argumentów funkcji zewnętrznych.

Wpływ: proaktywne zarządzanie pamięcią jako przepływ pracy

Dzięki wyposażeniu agenta kodującego w Narzędzia deweloperskie w Chrome dla agentów zespół Google Chat zautomatyzował bardzo wyspecjalizowane zadanie. Śledzenie złożonego wycieku pamięci zostało skrócone z kilku dni ręcznego przeszukiwania sterty do kilku minut automatycznej analizy.

Podobnie jak P2ER wdrożył „obowiązkową weryfikację empiryczną”, aby wymusić stabilność interfejsu, zespoły mogą przyjąć to podejście do kontroli pamięci jako proaktywne zabezpieczenie. Deweloperzy często ignorują problemy z pamięcią, zakładając, że występują one tylko w naprawdę dużych lub złożonych aplikacjach. Jednak jak pokazuje to studium przypadku, typowe wzorce, takie jak standardowe detektory zdarzeń lub błędy statyczne, mogą się niezauważalnie gromadzić. Integracja narzędzi takich jak Narzędzia deweloperskie w Chrome dla agentów z codziennym cyklem programowania przekształca AI z podstawowego generatora kodu w niezawodną bramkę jakości, która wykrywa ciche regresje, zanim trafią do środowiska produkcyjnego.