Cách Google Chat khắc phục hơn 30 vấn đề về bộ nhớ bằng Chrome DevTools cho tác nhân

Vatan Aggarwal
Vatan Aggarwal Software Engineer

Ngày xuất bản: 29 tháng 10 năm 2026

Google Chat là một ứng dụng trang đơn lớn. Người dùng thường xuyên mở ứng dụng web trên thẻ trình duyệt trong nhiều ngày hoặc nhiều tuần. Trong các ứng dụng có thời gian hoạt động dài, tình trạng bộ nhớ phía máy khách là mối quan tâm chính. Các hàm đóng không được thu thập và cây DOM tách rời tích tụ chậm, cuối cùng ảnh hưởng đến hiệu suất giao diện người dùng, tạm dừng thu gom rác hoặc sự cố thẻ trên các thiết bị bị hạn chế.

Để giải quyết vấn đề này, nhóm Google Chat đã triển khai một sáng kiến nhằm phát hiện và khắc phục các vấn đề về bộ nhớ. Họ đã sử dụng Chrome DevTools cho tác nhân, bao gồm cả máy chủ MCP của DevTools, để biến một quy trình thủ công khó khăn thành một quy trình tự động.

Thách thức: Mò kim trong đống cỏ khô 500 MB

Để xác định chính xác lý do một đối tượng bị rò rỉ được giữ lại trong bộ nhớ, bạn cần theo dõi các đường dẫn giữ lại thông qua mã đã được rút gọn nhiều. Mặc dù những điểm cải tiến gần đây đối với Chrome DevTools Memory panel (ví dụ: tính năng lọc cạnh nâng cao và truy vấn đối tượng) giúp việc này trở nên dễ dàng hơn, nhưng việc thực hiện thủ công trên một bản snapshot 500 MB vẫn có thể chậm và gây mệt mỏi.

Để khắc phục rò rỉ, bạn cần có hai nhóm kiến thức riêng biệt:

  • Bạn cần hiểu cách công cụ JavaScript V8 xử lý mã byte đã biên dịch.
  • Bạn cũng cần hiểu rõ cách quản lý trạng thái cụ thể và vòng đời thành phần của ứng dụng.

Việc kết nối hai tập hợp kiến thức riêng biệt này theo cách thủ công thường mất nhiều ngày điều tra và cộng tác với nhiều nhà phát triển cho mỗi lần rò rỉ.

Giải pháp: Điều hướng vùng nhớ khối xếp bằng một tác nhân

Nhóm nhận ra rằng mặc dù các mô hình ngôn ngữ lớn rất giỏi suy luận trên dữ liệu có cấu trúc, nhưng chúng cần các công cụ phù hợp để xử lý các tệp lớn trong khi vẫn duy trì mức tiêu thụ mã thông báo hợp lý. Bằng cách kết nối Google Antigravity với máy chủ MCP nguồn mở của Chrome DevTools khi bật các công cụ bộ nhớ (--memoryDebugging=true), họ đã cấp cho tác nhân quyền truy cập có lập trình trực tiếp vào vùng nhớ khối xếp V8.

Thay vì nhà phát triển phải mở rộng cây đối tượng giữ lại theo cách thủ công trong bảng điều khiển Bộ nhớ của Chrome DevTools, tác nhân đã truy vấn trực tiếp ảnh chụp nhanh. Nhóm đã sử dụng một câu lệnh đơn giản để bắt đầu quy trình:

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

Bằng cách sử dụng Kỹ năng gỡ lỗi bộ nhớ, tác nhân đã thực thi một quy trình làm việc có cấu trúc bằng các công cụ MCP cụ thể:

  1. Chụp hoặc tải ảnh chụp nhanh: Sử dụng các công cụ như take_heapsnapshot hoặc đọc tệp hiện có.
  2. Phân tích cấu trúc vùng nhớ khối xếp: Công cụ này quét các nguồn rò rỉ phổ biến bằng cách sử dụng get_heapsnapshot_details và compare_heapsnapshots.
  3. Trace retaining paths (Truy vết đường dẫn giữ lại): Công cụ này sử dụng get_heapsnapshot_retaining_paths để quay lại cây đối tượng chi phối đến gốc thu gom rác.
  4. Tạo bản sửa lỗi: Công cụ này tham chiếu chéo các đường dẫn giữ lại với mã nguồn của ứng dụng để đưa ra các bản vá có độ chính xác cao.

Thông tin chi tiết về kỹ thuật: Ba mẫu rò rỉ bộ nhớ phổ biến

Tác nhân này đã xác định được hơn 30 trường hợp rò rỉ riêng biệt. Sau đây là 3 mẫu có tác động mạnh nhất mà tác nhân tìm thấy, đại diện cho mức tiết kiệm bộ nhớ lớn nhất và các mẫu chống kiến trúc phổ biến nhất trong các ứng dụng web hiện đại.

Bẫy dấu vết ngăn xếp lỗi tĩnh

Tác nhân xác định rằng lỗi rò rỉ lớn nhất có liên quan đến các đối tượng lỗi được phân bổ trước, giữ lại vĩnh viễn gần 20 MB bộ nhớ. Ví dụ: một hằng số lỗi tĩnh (EMPTY_KEY_ERROR) đã giữ lại 14, 1 MB.

Vấn đề: Trong các công cụ JavaScript hiện đại như V8, việc tạo đối tượng Error sẽ chụp nhanh ngăn xếp lệnh gọi hiện tại để hỗ trợ gỡ lỗi. Kích thước được giữ lại lớn xảy ra do chính cơ chế này:

  • Lỗi là một hằng số chung: Lỗi này được tạo thực thể trong quá trình đánh giá mô-đun ban đầu.
  • Công cụ JavaScript đã thu thập ngăn xếp: Hàm đang hoạt động trên ngăn xếp tại thời điểm đó là trình bao bọc khởi chạy chính của ứng dụng (Biểu thức hàm được gọi ngay lập tức hoặc IIFE).
  • Mảng đã giữ lại bộ nhớ: Vì ứng dụng không bao giờ thu gom rác cho lỗi, nên mảng dấu vết ngăn xếp nội bộ của ứng dụng đã giữ lại tham chiếu vĩnh viễn đến hàm khởi chạy của gói chính.
// 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;
  }
}

Cách khắc phục: Nhóm đã thay đổi mã để tạo lỗi một cách linh động khi xảy ra lỗi. Nếu thực sự cần có lỗi tĩnh có thể sử dụng lại, thì việc xoá ngăn xếp một cách rõ ràng (EMPTY_KEY_ERROR.stack = '';) sẽ giải phóng ngay các khung nội bộ V8 đã thu thập.

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

Mở rộng trình nghe sự kiện cửa sổ toàn cục

Tác nhân đã tìm thấy vấn đề về việc điều chỉnh bộ nhớ liên quan đến visibilitychange trình nghe sự kiện. Mặc dù đây không phải là một lỗi "rò rỉ" nghiêm trọng (vì cuối cùng chúng sẽ được dọn dẹp khi thành phần bị ngắt kết nối), nhưng việc này làm tăng mức sử dụng bộ nhớ vì có một trình nghe cho mỗi thông báo trên màn hình. Ảnh chụp nhanh cho thấy 1.142 trình nghe sự kiện tích hợp sẵn, trong đó có 128 trình nghe đang hoạt động được đính kèm trực tiếp vào đối tượng cửa sổ chung.

Vấn đề: Trong JavaScript, lệnh gọi lại của trình nghe sự kiện là các hàm bao đóng. Một hàm bao đóng giữ lại các tham chiếu đến phạm vi từ vựng bao quanh để đảm bảo các biến mà hàm này cần vẫn có thể truy cập được. Tác nhân đã theo dõi 126 trình nghe này và tìm ra một thành phần duy nhất: RelativeTimeController. Mỗi dấu thời gian trong giao diện người dùng của Chat (ví dụ: "5 phút trước") quản lý trình điều khiển riêng, trình điều khiển này đã đăng ký một trình nghe chế độ hiển thị riêng tư để cập nhật thời gian. Vì mỗi trình nghe đều liên kết thực thể trình điều khiển của nó với gốc cửa sổ, nên một mạng lưới dày đặc các hàm đóng đang hoạt động sẽ chiếm một lượng bộ nhớ đáng kể trong suốt thời gian tồn tại của chuỗi trò chuyện hiển thị.

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

Cách khắc phục: Nhóm đã tập trung hoá các sự kiện cửa sổ chung. TimerService dùng chung hiện đăng ký chính xác một trình nghe chế độ hiển thị trên cửa sổ và truyền tin cập nhật đến tất cả trình điều khiển dấu thời gian đang hoạt động. Việc này ngay lập tức thu gọn 126 trình nghe xuống còn đúng 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
  }
}

SDK của bên thứ ba và trình giữ lại iframe được nhúng

Nút DOM đã tách là những phần tử đã bị xoá khỏi cây tài liệu nhưng vẫn được một biến JavaScript tham chiếu. Nếu một phần tử duy nhất trong cây DOM đã tách được một biến gốc giữ lại, thì toàn bộ cây đã tách sẽ không thể được thu hồi.

Vấn đề: Khi người dùng di chuyển giữa các không gian trò chuyện, nhân viên hỗ trợ nhận thấy rằng toàn bộ cây DOM tách rời vẫn còn trong bộ nhớ. Công cụ này đã truy vết đường dẫn giữ lại đến một trình phát nội dung nghe nhìn được nhúng trong giao diện người dùng của không gian trò chuyện. Khi một không gian trò chuyện bị ngắt kết nối, thành phần giao diện người dùng sẽ bị xoá khỏi trang, nhưng SDK trình phát nội dung nghe nhìn cơ bản không bao giờ bị huỷ một cách rõ ràng. Thư viện bên thứ ba duy trì một từ điển chung nội bộ gồm các mã nhận dạng iframe đang hoạt động, giúp DOM đã tách vẫn hoạt động.

Cách khắc phục: Đảm bảo tất cả iframe được nhúng và tiện ích của bên thứ ba đều liên kết với vòng đời xử lý thành phần. Việc gọi rõ ràng phương thức .destroy() trên trình phát sẽ xoá mục nhập khỏi sổ đăng ký SDK chung và cho phép DOM được thu gom rác.

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

Các mẫu khác

Mặc dù 3 mẫu nêu trên có tác động lớn nhất về mặt tiết kiệm megabyte thô, nhưng tác nhân này cũng phát hiện và khắc phục hơn 27 lỗi rò rỉ khác. Một số tình huống như sau:

  • Dữ liệu đo từ xa về mảng hình ảnh: Trường hợp cây DOM tách rời được duy trì hoạt động bằng các mảng chẩn đoán tuỳ chỉnh theo dõi các chỉ số hình ảnh trong màn hình đầu tiên
  • Lỗi vòng đời thành phần khung: Khi trình đơn thả xuống tuỳ chỉnh được đưa lên phần nội dung tài liệu, bỏ qua việc tháo dỡ thành phần mẹ ban đầu
  • Bẫy đóng đối tượng đối số ngầm định: Trong đó các hàm đóng bên trong vô tình thu thập toàn bộ mảng đối số của các hàm bên ngoài

Tác động: Quản lý bộ nhớ chủ động dưới dạng một quy trình công việc

Bằng cách trang bị cho tác nhân lập trình công cụ Chrome DevTools cho tác nhân, nhóm Google Chat đã tự động hoá một nhiệm vụ có tính chuyên môn cao. Thời gian theo dõi một lỗi rò rỉ bộ nhớ phức tạp đã giảm từ nhiều ngày duyệt vùng nhớ khối xếp theo cách thủ công xuống còn vài phút phân tích tự động.

Tương tự như việc P2ER triển khai "Xác minh thực nghiệm bắt buộc" để thực thi độ ổn định của giao diện người dùng, các nhóm có thể áp dụng phương pháp kiểm tra bộ nhớ này làm biện pháp bảo vệ chủ động. Nhà phát triển thường bỏ qua các vấn đề về bộ nhớ, cho rằng các vấn đề này chỉ xảy ra trong các ứng dụng thực sự lớn hoặc phức tạp. Tuy nhiên, như nghiên cứu điển hình này cho thấy, các mẫu phổ biến như trình nghe sự kiện chuẩn hoặc lỗi tĩnh có thể tích luỹ một cách thầm lặng. Việc tích hợp các công cụ như Chrome DevTools cho tác nhân vào chu trình phát triển hằng ngày sẽ biến AI từ một trình tạo mã cơ bản thành một cổng chất lượng đáng tin cậy, giúp phát hiện các lỗi hồi quy thầm lặng trước khi chúng được đưa vào môi trường thực tế.