Khắc phục sự cố về bộ nhớ

Kayce Basques
Kayce Basques

Tìm hiểu cách sử dụng Chrome và Công cụ cho nhà phát triển để tìm các vấn đề về bộ nhớ ảnh hưởng đến hiệu suất trang, bao gồm cả rò rỉ bộ nhớ, tăng dung lượng bộ nhớ và thu gom rác thường xuyên.

Tóm tắt

  • Tìm hiểu mức sử dụng bộ nhớ của trang bằng Trình quản lý tác vụ của Chrome.
  • Trực quan hoá mức sử dụng bộ nhớ theo thời gian bằng bản ghi Dòng thời gian.
  • Xác định các cây DOM đã tách (một nguyên nhân phổ biến gây rò rỉ bộ nhớ) bằng snapshot của vùng nhớ heap.
  • Tìm hiểu thời điểm bộ nhớ mới được phân bổ trong vùng nhớ khối xếp JS bằng bản ghi Dòng thời gian phân bổ.
  • Xác định các phần tử đã tách được giữ lại bằng tham chiếu JavaScript.

Tổng quan

Theo tinh thần của mô hình hiệu suất RAIL, trọng tâm của các nỗ lực cải thiện hiệu suất phải là người dùng.

Các vấn đề về bộ nhớ rất quan trọng vì người dùng thường nhận thấy được. Người dùng có thể nhận thấy các vấn đề về bộ nhớ theo những cách sau:

  • Hiệu suất của trang ngày càng kém đi theo thời gian. Đây có thể là dấu hiệu của lỗi rò rỉ bộ nhớ. Rò rỉ bộ nhớ là khi một lỗi trong trang khiến trang dần sử dụng ngày càng nhiều bộ nhớ theo thời gian.
  • Hiệu suất của trang luôn kém. Đây có thể là dấu hiệu của tình trạng phình bộ nhớ. Tình trạng phình bộ nhớ xảy ra khi một trang sử dụng nhiều bộ nhớ hơn mức cần thiết để đạt được tốc độ trang tối ưu.
  • Hiệu suất của trang bị chậm trễ hoặc có vẻ như thường xuyên tạm dừng. Đây có thể là dấu hiệu của việc thu gom rác thường xuyên. Thu gom rác là khi trình duyệt lấy lại bộ nhớ. Trình duyệt sẽ quyết định thời điểm thực hiện việc này. Trong quá trình thu thập, tất cả quá trình thực thi tập lệnh đều bị tạm dừng. Vì vậy, nếu trình duyệt thu gom rác nhiều, thì quá trình thực thi tập lệnh sẽ bị tạm dừng nhiều lần.

Phình bộ nhớ: bao nhiêu là "quá nhiều"?

Rò rỉ bộ nhớ là một khái niệm dễ định nghĩa. Nếu một trang web đang dần sử dụng ngày càng nhiều bộ nhớ, thì tức là bạn đã bị rò rỉ. Tuy nhiên, tình trạng phình bộ nhớ khó xác định hơn một chút. Trường hợp nào được coi là "sử dụng quá nhiều bộ nhớ"?

Không có con số chính xác ở đây vì các thiết bị và trình duyệt khác nhau có các chức năng khác nhau. Cùng một trang chạy mượt mà trên điện thoại thông minh cao cấp có thể gặp sự cố trên điện thoại thông minh cấp thấp.

Điều quan trọng ở đây là sử dụng mô hình RAIL và tập trung vào người dùng. Tìm hiểu xem người dùng của bạn thường dùng thiết bị nào, sau đó thử nghiệm trang của bạn trên những thiết bị đó. Nếu trải nghiệm liên tục kém, thì có thể trang đang vượt quá khả năng bộ nhớ của các thiết bị đó.

Theo dõi mức sử dụng bộ nhớ theo thời gian thực bằng Trình quản lý tác vụ Chrome

Hãy sử dụng Trình quản lý tác vụ của Chrome làm điểm xuất phát để điều tra vấn đề về bộ nhớ. Trình quản lý tác vụ là một công cụ giám sát theo thời gian thực, cho bạn biết mức sử dụng bộ nhớ của một trang.

  1. Nhấn tổ hợp phím Shift+Esc hoặc chuyển đến trình đơn chính của Chrome rồi chọn Công cụ khác > Trình quản lý tác vụ để mở Trình quản lý tác vụ.

    Mở Trình quản lý tác vụ.

  2. Nhấp chuột phải vào tiêu đề bảng của Trình quản lý tác vụ rồi bật Bộ nhớ JavaScript.

    Bật bộ nhớ JS trong tiêu đề của Trình quản lý tác vụ.

Hai cột này cho bạn biết những thông tin khác nhau về cách trang của bạn sử dụng bộ nhớ:

  • Cột Mức sử dụng bộ nhớ thể hiện bộ nhớ của hệ điều hành. Các nút DOM được lưu trữ trong bộ nhớ của hệ điều hành. Nếu giá trị này tăng lên, thì các nút DOM đang được tạo.
  • Cột Bộ nhớ JavaScript đại diện cho vùng nhớ khối xếp JS. Cột này chứa hai giá trị. Giá trị bạn quan tâm là số lượng trực tiếp (số trong ngoặc đơn). Số lượng đối tượng đang hoạt động thể hiện dung lượng bộ nhớ mà các đối tượng có thể truy cập trên trang của bạn đang sử dụng. Nếu con số này tăng lên, thì tức là các đối tượng mới đang được tạo hoặc các đối tượng hiện có đang tăng lên.

    Trình quản lý tác vụ khi bật tiêu đề bộ nhớ JavaScript.

Trực quan hoá lỗi rò rỉ bộ nhớ bằng bản ghi Hiệu suất

Bạn cũng có thể sử dụng bảng điều khiển Hiệu suất làm một điểm xuất phát khác trong quá trình điều tra. Bảng điều khiển Hiệu suất giúp bạn hình dung mức sử dụng bộ nhớ của một trang theo thời gian.

  1. Mở bảng điều khiển Hiệu suất trong Công cụ cho nhà phát triển.
  2. Chọn hộp đánh dấu Memory (Bộ nhớ).
  3. Tạo bản ghi âm.

Để minh hoạ bản ghi bộ nhớ Hiệu suất, hãy xem xét đoạn mã sau:

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Mỗi lần nhấn nút được tham chiếu trong mã, mười nghìn nút div sẽ được thêm vào phần nội dung tài liệu và một chuỗi gồm một triệu ký tự x sẽ được đẩy vào mảng x. Khi chạy mã này, bạn sẽ nhận được bản ghi Dòng thời gian như ảnh chụp màn hình sau:

Ví dụ về mức tăng trưởng đơn giản.

Trước tiên, hãy cùng tìm hiểu về giao diện người dùng. Biểu đồ HEAP trong ngăn Overview (Tổng quan) (bên dưới NET) thể hiện vùng nhớ khối xếp JS. Bên dưới ngăn Overview (Tổng quan) là ngăn Counter (Bộ đếm). Tại đây, bạn có thể xem mức sử dụng bộ nhớ được chia nhỏ theo vùng nhớ khối xếp JS (giống như biểu đồ HEAP trong ngăn Tổng quan), tài liệu, nút DOM, trình nghe và bộ nhớ GPU. Khi bạn bỏ đánh dấu một hộp, hộp đó sẽ bị ẩn khỏi biểu đồ.

Bây giờ, hãy phân tích mã so với ảnh chụp màn hình. Nếu xem bộ đếm nút (biểu đồ màu xanh lục), bạn có thể thấy bộ đếm này khớp hoàn toàn với mã. Số lượng nút tăng theo các bước riêng biệt. Bạn có thể giả định rằng mỗi lần tăng số lượng nút là một lệnh gọi đến grow(). Biểu đồ vùng nhớ khối xếp JS (biểu đồ màu xanh dương) không đơn giản như vậy. Để phù hợp với các phương pháp hay nhất, lần giảm đầu tiên thực ra là một lần thu gom rác bắt buộc (đạt được bằng cách nhấn nút thu gom rác). Khi quá trình ghi tiến triển, bạn có thể thấy kích thước vùng nhớ khối xếp JS tăng đột biến. Điều này là tự nhiên và đúng như dự kiến: mã JavaScript đang tạo các nút DOM mỗi khi người dùng nhấp vào nút và thực hiện nhiều thao tác khi tạo chuỗi một triệu ký tự. Điều quan trọng ở đây là vùng nhớ khối xếp JS kết thúc cao hơn mức bắt đầu ("bắt đầu" ở đây là điểm sau khi buộc thu gom rác). Trong thực tế, nếu bạn thấy mẫu kích thước vùng nhớ khối xếp JS hoặc kích thước nút tăng lên, thì có thể là do rò rỉ bộ nhớ.

Phát hiện lỗi rò rỉ bộ nhớ của cây DOM tách rời bằng Ảnh chụp nhanh vùng nhớ khối xếp

Nút DOM chỉ có thể được thu gom rác khi không có tham chiếu đến nút đó từ cây DOM của trang hoặc mã JavaScript. Một nút được cho là "bị tách rời" khi bị xoá khỏi cây DOM nhưng một số JavaScript vẫn tham chiếu đến nút đó. Các nút DOM đã tách là nguyên nhân phổ biến gây ra rò rỉ bộ nhớ. Phần này hướng dẫn bạn cách sử dụng công cụ lập hồ sơ ảnh chụp nhanh của vùng nhớ khối xếp của DevTools để xác định các nút tách rời.

Dưới đây là một ví dụ đơn giản về các nút DOM tách rời.

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

Khi nhấp vào nút được tham chiếu trong mã, bạn sẽ tạo một nút ul có 10 phần tử con li. Các nút này được mã tham chiếu nhưng không tồn tại trong cây DOM, vì vậy chúng bị tách rời.

Ảnh chụp nhanh vùng nhớ khối xếp là một cách để xác định các nút tách rời. Đúng như tên gọi, snapshot của vùng nhớ heap cho bạn biết cách phân phối bộ nhớ giữa các đối tượng JS và nút DOM của trang tại thời điểm chụp nhanh.

Để tạo snapshot của vùng nhớ heap, hãy mở DevTools rồi chuyển đến bảng điều khiển Memory (Bộ nhớ), chọn nút chọn Heap Snapshot (Snapshot của vùng nhớ heap), sau đó nhấn nút Take snapshot (Chụp nhanh).

Nút chọn Take heap snapshot (snapshot của vùng nhớ heap) được chọn.

Có thể mất chút thời gian để xử lý và tải thông tin tổng quan nhanh. Sau khi hoàn tất, hãy chọn ảnh chụp nhanh đó trong bảng điều khiển bên trái (có tên là Heap snapshots (snapshot của vùng nhớ heap)).

Nhập Detached vào hộp nhập Bộ lọc lớp để tìm cây DOM tách rời.

Lọc các nút tách rời.

Mở rộng dấu mũ để kiểm tra cây tách rời.

Điều tra một cây tách rời.

Nhấp vào một nút để tìm hiểu thêm. Trong ngăn Objects (Đối tượng), bạn có thể xem thêm thông tin về mã đang tham chiếu đến đối tượng đó. Ví dụ: trong ảnh chụp màn hình sau, bạn có thể thấy biến detachedTree đang tham chiếu đến nút. Để khắc phục lỗi rò rỉ bộ nhớ cụ thể này, bạn sẽ nghiên cứu mã sử dụng detachedTree và đảm bảo rằng mã đó xoá tham chiếu đến nút khi không còn cần thiết.

Điều tra một nút tách rời.

Xác định lỗi rò rỉ bộ nhớ vùng nhớ khối xếp JS bằng Tiến trình phân bổ

Allocation Timeline (Dòng thời gian phân bổ) là một công cụ khác có thể giúp bạn theo dõi rò rỉ bộ nhớ trong vùng nhớ khối xếp JS.

Để minh hoạ Dòng thời gian phân bổ, hãy xem xét đoạn mã sau:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Mỗi lần người dùng nhấn vào nút được tham chiếu trong mã, một chuỗi gồm một triệu ký tự sẽ được thêm vào mảng x.

Để ghi lại Dòng thời gian phân bổ, hãy mở Công cụ cho nhà phát triển, chuyển đến bảng điều khiển Bộ nhớ, chọn nút chọn Phân bổ trên dòng thời gian, nhấn vào nút Ghi, thực hiện hành động mà bạn nghi ngờ là gây ra rò rỉ bộ nhớ, sau đó nhấn vào nút Dừng ghi khi bạn hoàn tất.

Khi bạn đang ghi, hãy chú ý xem có thanh màu xanh dương nào xuất hiện trên Allocation Timeline (Dòng thời gian phân bổ) hay không, như trong ảnh chụp màn hình sau đây.

Các lượt phân bổ mới trong dòng thời gian hiệu suất.

Các thanh màu xanh dương đó thể hiện các lượt phân bổ bộ nhớ mới. Những lượt phân bổ bộ nhớ mới đó là các đối tượng có thể gây rò rỉ bộ nhớ. Bạn có thể thu phóng trên một thanh để lọc ngăn Constructor (Hàm khởi tạo) để chỉ hiển thị các đối tượng được phân bổ trong khung thời gian đã chỉ định.

Dòng thời gian phân bổ được phóng to.

Mở rộng đối tượng và nhấp vào giá trị của đối tượng đó để xem thêm thông tin chi tiết trong ngăn Object (Đối tượng). Ví dụ: trong ảnh chụp màn hình bên dưới, bằng cách xem thông tin chi tiết về đối tượng mới được phân bổ, bạn có thể thấy đối tượng đó được phân bổ cho biến x trong phạm vi Window.

Thông tin chi tiết về đối tượng của một mảng chuỗi.

Điều tra việc phân bổ bộ nhớ theo hàm

Sử dụng loại hồ sơ Lấy mẫu phân bổ trong bảng điều khiển Bộ nhớ để xem mức phân bổ bộ nhớ theo hàm JavaScript.

Trình phân tích tài nguyên lấy mẫu phân bổ trong bảng điều khiển Bộ nhớ.

  1. Chọn nút chọn Allocation sampling (Lấy mẫu phân bổ). Nếu có một worker trên trang, bạn có thể chọn worker đó làm mục tiêu phân tích tài nguyên trong cửa sổ Select JavaScript VM instance (Chọn thực thể máy ảo JavaScript).
  2. Nhấn nút Start (Bắt đầu).
  3. Thực hiện các thao tác trên trang mà bạn muốn điều tra.
  4. Nhấn nút Stop (Dừng) khi bạn đã hoàn tất mọi thao tác.

Công cụ cho nhà phát triển cho bạn thấy thông tin chi tiết về việc phân bổ bộ nhớ theo hàm. Chế độ xem mặc định là Heavy (Bottom Up) (Nặng (Từ dưới lên)), hiển thị các hàm đã phân bổ nhiều bộ nhớ nhất ở trên cùng.

Trang kết quả của hồ sơ phân bổ.

Xác định các đối tượng được tham chiếu JS giữ lại

Hồ sơ Phần tử đã tách cho bạn thấy các phần tử đã tách vẫn tồn tại vì chúng được mã JavaScript tham chiếu.

Ghi lại hồ sơ Phần tử tách rời để xem chính xác các nút HTML và số lượng nút.

Ví dụ về hồ sơ phần tử đã tách.

Phát hiện các hoạt động thu gom rác thường xuyên

Nếu trang của bạn có vẻ thường xuyên tạm dừng, thì có thể bạn đang gặp vấn đề về việc dọn rác.

Bạn có thể sử dụng Trình quản lý tác vụ của Chrome hoặc bản ghi bộ nhớ trên Dòng thời gian để phát hiện các hoạt động thu gom rác thường xuyên. Trong Trình quản lý tác vụ, các giá trị Bộ nhớ hoặc Bộ nhớ JavaScript thường xuyên tăng và giảm thể hiện các hoạt động thu gom rác thường xuyên. Trong bản ghi Dòng thời gian, biểu đồ số lượng nút hoặc vùng nhớ khối xếp JS thường xuyên tăng và giảm cho biết quá trình dọn rác thường xuyên diễn ra.

Sau khi xác định được vấn đề, bạn có thể sử dụng bản ghi Dòng thời gian phân bổ để tìm ra vị trí bộ nhớ đang được phân bổ và hàm nào đang gây ra việc phân bổ.