Kiểm tra mức sử dụng bộ nhớ của ứng dụng bằng Trình phân tích bộ nhớ

Trình phân tích bộ nhớ là một thành phần trong Trình phân tích tài nguyên Android (Android Profiler) giúp bạn xác định những tình trạng rò rỉ và nhồi nhét bộ nhớ có thể khiến ứng dụng của bạn kết xuất gián đoạn, bị treo và thậm chí gặp sự cố. Tính năng này cho bạn thấy biểu đồ theo thời gian thực về mức sử dụng bộ nhớ của ứng dụng, đồng thời cho phép bạn ghi lại tệp báo lỗi, thu thập rác và theo dõi cơ cấu phân bổ bộ nhớ.

Để mở Trình phân tích bộ nhớ, hãy làm theo các bước sau:

  1. Nhấp vào View (Xem) > Tool Windows (Cửa sổ công cụ) > Profiler (Trình phân tích tài nguyên) (bạn cũng có thể nhấp vào Profile (Phân tích tài nguyên) trong thanh công cụ).
  2. Chọn thiết bị và quy trình ứng dụng mà bạn muốn phân tích trong thanh công cụ Android Profiler (Trình phân tích tài nguyên Android). Nếu bạn đã kết nối thiết bị qua USB nhưng không thấy thiết bị đó trong danh sách, hãy đảm bảo bạn đã bật tính năng gỡ lỗi qua USB.
  3. Nhấp vào vị trí bất kỳ trong tiến trình MEMORY (BỘ NHỚ) để mở Memory Profiler (Trình phân tích bộ nhớ).

Ngoài ra, bạn có thể kiểm tra bộ nhớ của ứng dụng qua dòng lệnh bằng dumpsys cũng như xem các sự kiện GC trong logcat.

Tại sao bạn nên phân tích bộ nhớ ứng dụng

Android cung cấp một môi trường bộ nhớ được quản lý – khi nhận thấy ứng dụng của bạn không còn sử dụng một số đối tượng, trình thu gom rác (garbage collector) sẽ giải phóng bộ nhớ không dùng đến trở về vùng nhớ khối xếp. Tuy Android liên tục cải tiến cách tìm bộ nhớ không dùng đến, nhưng đến một thời điểm nào đó trên mọi phiên bản Android, hệ thống sẽ phải tạm dừng mã của bạn trong giây lát. Trong hầu hết trường hợp, bạn sẽ không nhận thấy việc tạm dừng. Tuy nhiên, nếu ứng dụng của bạn phân bổ bộ nhớ nhanh hơn tốc độ hệ thống thu thập bộ nhớ, thì ứng dụng của bạn có thể bị gián đoạn trong khi trình thu gom giải phóng đủ bộ nhớ để đáp ứng mức phân bổ. Tình trạng trễ này có thể khiến ứng dụng của bạn bỏ qua khung hình cũng như làm chậm khung hình.

Ngay cả khi ứng dụng của bạn không bị chậm lại, nếu bị rò rỉ bộ nhớ, thì ứng dụng vẫn có thể giữ lại bộ nhớ đó ngay cả khi đang ở chế độ nền. Tình trạng này có thể làm giảm hiệu suất của bộ nhớ còn lại trong hệ thống do các sự kiện buộc thu thập rác không cần thiết. Cuối cùng, hệ thống buộc phải đóng quy trình ứng dụng để lấy lại bộ nhớ. Sau đó, khi người dùng trở lại ứng dụng của bạn, ứng dụng phải khởi động lại hoàn toàn.

Để ngăn chặn những sự cố như vậy, bạn nên sử dụng Trình phân tích bộ nhớ để làm những việc sau:

  • Tìm những mẫu phân bổ bộ nhớ không mong muốn trong tiến trình có thể gây ra vấn đề về hiệu suất.
  • Kết xuất dữ liệu của vùng nhớ khối xếp Java để xem những đối tượng nào đang dùng hết bộ nhớ tại một thời điểm nào đó. Nhiều tệp báo lỗi trong một khoảng thời gian dài có thể giúp xác định tình trạng rò rỉ bộ nhớ.
  • Ghi lại quá trình phân bổ bộ nhớ trong khi người dùng tương tác theo cách bình thường và theo cách cực đoan để xác định chính xác nơi mã của bạn đang phân bổ quá nhiều đối tượng trong một thời gian ngắn hoặc phân bổ các đối tượng đã bị rò rỉ.

Để biết thông tin về các phương pháp lập trình có thể làm giảm mức sử dụng bộ nhớ của ứng dụng, hãy đọc nội dung Quản lý bộ nhớ của ứng dụng.

Tổng quan về Trình phân tích bộ nhớ

Trong lần đầu mở Trình phân tích bộ nhớ, bạn sẽ thấy tiến trình chi tiết về mức sử dụng bộ nhớ của ứng dụng cũng như thấy các công cụ để buộc thu gom rác, ghi lại tệp báo lỗi và ghi lại quá trình phân bổ bộ nhớ.

Hình 1. Trình phân tích bộ nhớ

Như thể hiện trong hình 1, khung hiển thị mặc định của Trình phân tích bộ nhớ có những mục sau:

  1. Một nút để buộc thực hiện một sự kiện thu gom rác.
  2. Một nút để Ghi lại một tệp báo lỗi.

    Lưu ý: Nút để ghi lại quá trình phân bổ bộ nhớ chỉ xuất hiện ở bên phải nút tệp báo lỗi khi kết nối với một thiết bị chạy Android 7.1 (API cấp 25) trở xuống.

  3. Một trình đơn thả xuống để chỉ định tần suất trình phân tích tài nguyên ghi lại quá trình phân bổ bộ nhớ. Bạn có thể chọn tuỳ chọn thích hợp để cải thiện hiệu suất ứng dụng trong khi phân tích.
  4. Các nút để phóng to/thu nhỏ tiến trình.
  5. Một nút để chuyển tới dữ liệu bộ nhớ thực tế.
  6. Tiến trình sự kiện cho thấy trạng thái hoạt động, sự kiện nhập của người dùng và sự kiện xoay màn hình.
  7. Tiến trình sử dụng bộ nhớ bao gồm:
    • Một biểu đồ xếp chồng về lượng bộ nhớ mà mỗi danh mục bộ nhớ đang sử dụng, biểu thị bằng trục y ở bên trái và khoá màu ở trên cùng.
    • Một đường đứt nét thể hiện số lượng đối tượng được phân bổ, biểu thị theo trục y ở bên phải.
    • Một biểu tượng cho mỗi sự kiện thu gom rác.

Tuy nhiên, nếu bạn đang sử dụng một thiết bị chạy Android 7.1 trở xuống, thì theo mặc định không phải dữ liệu phân tích nào cũng xuất hiện. Nếu thấy thông báo cho biết "Advanced profiling is unavailable for the selected process" ("Không thể phân tích nâng cao cho quy trình đã chọn"), bạn cần bật tính năng phân tích nâng cao để xem được những thông tin sau:

  • Tiến trình sự kiện
  • Số lượng đối tượng được phân bổ
  • Sự kiện thu gom rác

Trên Android 8.0 trở lên, tính năng phân tích nâng cao sẽ luôn bật cho các ứng dụng có thể gỡ lỗi.

Cách tính bộ nhớ

Các con số bạn thấy ở đầu Trình phân tích bộ nhớ (hình 2) được dựa trên tất cả các trang bộ nhớ riêng tư mà ứng dụng của bạn đã cam kết, theo hệ thống Android. Số liệu này không bao gồm các trang được chia sẻ với hệ thống hoặc các ứng dụng khác.

Hình 2. Chú giải về mức sử dụng bộ nhớ ở đầu Trình phân tích bộ nhớ

Có các danh mục sau trong phần mức sử dụng bộ nhớ:

  • Java: Mức sử dụng bộ nhớ của các đối tượng được phân bổ qua mã Java hoặc Kotlin.
  • Native (Mã gốc): Mức sử dụng bộ nhớ của các đối tượng được phân bổ qua mã C hoặc C++.

    Ngay cả khi không dùng C++ trong ứng dụng, ở đây bạn vẫn có thể thấy bộ nhớ mà mã gốc dùng (native memory) do khung Android sử dụng loại bộ nhớ này để thay mặt bạn xử lý nhiều thao tác, chẳng hạn như khi xử lý thành phần hình ảnh hoặc các thành phần đồ hoạ khác (mặc dù bạn viết mã bằng Java hoặc Kotlin).

  • Graphics (Đồ hoạ): Mức sử dụng bộ nhớ cho các hàng đợi bộ đệm đồ hoạ để hiện pixel lên màn hình, bao gồm cả bề mặt GL, kết cấu GL, v.v. (Xin lưu ý rằng đây là bộ nhớ dùng chung với CPU, không phải bộ nhớ GPU chuyên dụng.)

  • Stack (Ngăn xếp): Mức sử dụng bộ nhớ của cả ngăn xếp gốc và ngăn xếp Java trong ứng dụng của bạn. Số liệu này thường liên quan đến số lượng luồng mà ứng dụng của bạn đang chạy.

  • Code (Mã gốc): Mức sử dụng bộ nhớ của ứng dụng cho mã gốc và tài nguyên, chẳng hạn như mã byte dex, mã dex được tối ưu hoá hoặc biên dịch, thư viện .so và phông chữ.

  • Others (Khác): Mức sử dụng bộ nhớ của ứng dụng mà hệ thống không chắc cách phân loại.

  • Allocated (Đã phân bổ): Số lượng đối tượng Java/Kotlin mà ứng dụng của bạn đã phân bổ. Số liệu này không tính các đối tượng được phân bổ trong C hoặc C++.

    Khi kết nối với một thiết bị chạy Android 7.1 trở xuống, số liệu phân bổ này chỉ bắt đầu tại thời điểm Trình phân tích bộ nhớ kết nối với ứng dụng đang chạy của bạn. Vì vậy, mọi đối tượng được phân bổ trước khi bạn bắt đầu phân tích sẽ không được tính đến. Tuy nhiên, Android 8.0 trở lên có một công cụ phân tích trên thiết bị giúp theo dõi mọi mức phân bổ. Vì vậy, số liệu này luôn thể hiện tổng số đối tượng Java còn tồn đọng trong ứng dụng của bạn trên thiết bị Android 8.0 trở lên.

Khi so sánh với mức sử dụng bộ nhớ qua công cụ Android Monitor (Trình theo dõi Android) trước đó, Trình phân tích bộ nhớ mới sẽ ghi lại bộ nhớ của bạn theo cách khác, vì vậy, trông có vẻ như mức sử dụng bộ nhớ hiện tại của bạn cao hơn. Trình phân tích bộ nhớ theo dõi một số danh mục bổ sung giúp tăng tổng số, nhưng nếu bạn chỉ quan tâm đến bộ nhớ khối xếp Java thì số liệu "Java" sẽ tương đương với giá trị thể hiện trong công cụ trước đây. Mặc dù số liệu Java có thể không khớp chính xác với những gì bạn thấy trong Android Monitor (Trình theo dõi Android), nhưng số liệu mới có tính đến tất cả các trang bộ nhớ thực đã được phân bổ cho vùng nhớ khối xếp Java của ứng dụng kể từ khi được phát triển nhánh qua Zygote. Vì vậy, số liệu này thể hiện chính xác mức độ ứng dụng sử dụng bộ nhớ thực.

Xem cơ cấu phân bổ bộ nhớ

Cơ cấu phân bổ bộ nhớ cho bạn biết tình trạng phân bổ từng đối tượng Java và tham chiếu JNI trong bộ nhớ. Cụ thể, Trình phân tích bộ nhớ có thể cho bạn thấy những thông tin sau về tình trạng phân bổ đối tượng:

  • Loại đối tượng được phân bổ và mức sử dụng của những đối tượng đó.
  • Dấu vết ngăn xếp của mỗi mức phân bổ, bao gồm cả luồng chứa tương ứng.
  • Khi các đối tượng được giải phóng khỏi bộ nhớ (chỉ khi sử dụng thiết bị chạy Android 8.0 trở lên).

Để ghi lại mức phân bổ cho Java và Kotlin, hãy chọn Record Java / Kotlin allocations (Ghi lại mức phân bổ cho Java/Kotlin), sau đó chọn Record (Ghi). Nếu thiết bị đang chạy Android 8 trở lên, thì giao diện người dùng của Trình phân tích bộ nhớ sẽ chuyển sang một màn hình riêng cho thấy quá trình ghi đang diễn ra. Bạn có thể tương tác với tiến trình thu nhỏ ở phía trên bản ghi (ví dụ: để thay đổi phạm vi lựa chọn). Để hoàn tất quá trình ghi, hãy chọn biểu tượng Stop (Dừng) .

Hình ảnh minh hoạ quá trình phân bổ cho Java trong Trình phân tích bộ nhớ

Trên Android 7.1 trở xuống, trình phân tích bộ nhớ sử dụng tính năng ghi quá trình phân bổ cũ, cho thấy quá trình ghi trên tiến trình cho đến khi bạn nhấp vào Stop (Dừng).

Sau khi bạn chọn một vùng trên tiến trình (hoặc khi bạn hoàn tất một phiên ghi cho thiết bị chạy Android 7.1 trở xuống), danh sách đối tượng được phân bổ sẽ xuất hiện, được phân nhóm theo tên lớp và sắp xếp theo số lượng vùng nhớ khối xếp.

Để kiểm tra bản ghi mức phân bổ, hãy làm theo các bước sau:

  1. Duyệt xem danh sách để tìm những đối tượng có số lượng vùng nhớ khối xếp lớn bất thường và có thể bị rò rỉ. Để tìm các lớp đã xác định, hãy nhấp vào tiêu đề cột Class Name (Tên lớp) để sắp xếp theo thứ tự bảng chữ cái. Sau đó, hãy nhấp vào một tên lớp. Ngăn Instance View (Chế độ xem thực thể) xuất hiện ở bên phải cho thấy từng thực thể của lớp đó, như thể hiện trong hình 3.
    • Ngoài ra, bạn có thể tìm nhanh các đối tượng bằng cách nhấp vào biểu tượng Filter (Bộ lọc) hoặc bằng cách nhấn Control+F (Command+F trên Mac) rồi nhập tên lớp hoặc gói trong trường tìm kiếm. Bạn cũng có thể tìm kiếm theo tên phương thức nếu chọn Arrange by callstack (Sắp xếp theo ngăn xếp lệnh gọi) trong trình đơn thả xuống. Nếu bạn muốn sử dụng biểu thức chính quy, hãy đánh dấu hộp bên cạnh Regex (Biểu thức chính quy). Đánh dấu vào hộp bên cạnh Match case (Khớp chữ hoa chữ thường) nếu cụm từ tìm kiếm của bạn có phân biệt chữ hoa chữ thường.
  2. Trong ngăn Instance View (Chế độ xem thực thể), hãy nhấp vào một thực thể. Thẻ Call Stack (Ngăn xếp lệnh gọi) xuất hiện bên dưới, cho thấy vị trí nơi phân bổ thực thể đó cũng như luồng tương ứng.
  3. Trong thẻ Call Stack (Ngăn xếp lệnh gọi), bạn có thể nhấp chuột phải vào một dòng bất kỳ rồi chọn Jump to Source (Chuyển đến nguồn) để mở mã đó trong trình chỉnh sửa.

Hình 3. Thông tin chi tiết về từng đối tượng được phân bổ xuất hiện trong Instance View (Chế độ xem thực thể) ở bên phải

Bạn có thể sử dụng hai trình đơn phía trên danh sách đối tượng được phân bổ để chọn vùng nhớ khối xếp cần kiểm tra cũng như cách sắp xếp dữ liệu.

Trong trình đơn bên trái, hãy chọn vùng nhớ khối xếp cần kiểm tra:

  • default heap (vùng nhớ khối xếp mặc định): Khi hệ thống không chỉ định vùng nhớ khối xếp nào.
  • image heap (vùng nhớ khối xếp hình ảnh): Hình ảnh khởi động của hệ thống, chứa các lớp được tải trước trong thời gian khởi động. Mức phân bổ tại đây được đảm bảo sẽ không bao giờ di chuyển hoặc biến mất.
  • zygote heap (bộ nhớ khối xếp zygote): Vùng nhớ khối xếp dạng ngầm sao chép (copy-on-write) nơi quy trình ứng dụng được phát triển nhánh qua hệ thống Android.
  • app heap (vùng nhớ khối xếp ứng dụng): Vùng nhớ khối xếp chính nơi ứng dụng của bạn phân bổ bộ nhớ.
  • JNI heap (vùng nhớ khối xếp JNI): Vùng nhớ khối xếp cho biết nơi phân bổ và giải phóng lượt tham chiếu Giao diện gốc Java (JNI).

Trong trình đơn bên phải, hãy chọn cách sắp xếp các lượt phân bổ:

  • Arrange by class (Sắp xếp theo lớp): Nhóm tất cả lượt phân bổ dựa trên tên lớp. Đây là tuỳ chọn mặc định
  • Arrange by package (Sắp xếp theo gói): Nhóm tất cả lượt phân bổ dựa trên tên gói.
  • Arrange by callstack (Sắp xếp theo ngăn xếp lệnh gọi): Nhóm tất cả lượt phân bổ vào ngăn xếp lệnh gọi tương ứng.

Cải thiện hiệu suất của ứng dụng trong khi phân tích

Để cải thiện hiệu suất của ứng dụng trong quá trình phân tích, trình phân tích bộ nhớ sẽ lấy mẫu các lượt phân bổ bộ nhớ theo định kỳ theo mặc định. Khi thử nghiệm trên thiết bị chạy API cấp 26 trở lên, bạn có thể thay đổi hành vi này bằng cách sử dụng trình đơn thả xuống Allocation Tracking (Theo dõi quá trình phân bổ). Hiện có các tuỳ chọn sau:

  • Full (Đầy đủ): Ghi lại toàn bộ mức phân bổ bộ nhớ cho đối tượng. Đây là hành vi mặc định trong Android Studio 3.2 trở xuống. Nếu có một ứng dụng phân bổ rất nhiều đối tượng, có thể bạn sẽ quan sát thấy tình trạng ứng dụng bị chậm lại trong quá trình phân tích.
  • Sample (Lấy mẫu): Lấy mẫu mức phân bổ đối tượng trong bộ nhớ theo chu kỳ. Đây là tuỳ chọn mặc định và có ít tác động hơn đến hiệu suất của ứng dụng trong quá trình phân tích. Đối với ứng dụng phân bổ nhiều đối tượng trong một khoảng thời gian ngắn, có thể bạn vẫn thấy tình trạng ứng dụng bị chậm lại.
  • Off (Tắt): Ngừng theo dõi quá trình phân bổ bộ nhớ của ứng dụng.

Xem các lượt tham chiếu đến JNI toàn cục

Giao diện gốc Java (Java Native Interface – JNI) là một khung cho phép mã Java và mã gốc gọi lẫn nhau.

Các lượt tham chiếu JNI được mã gốc quản lý theo cách thủ công. Vì vậy, có thể có tình trạng các đối tượng Java do mã gốc sử dụng được duy trì hoạt động trong thời gian dài. Có thể không truy cập được một số đối tượng trên vùng nhớ khối xếp Java nếu lượt tham chiếu JNI bị loại bỏ nhưng trước đó chưa thể hiện rõ thao tác xoá. Ngoài ra, có thể bạn đã sử dụng hết hạn mức tham chiếu JNI toàn cục.

Để khắc phục các vấn đề như vậy, hãy sử dụng chế độ xem JNI heap (vùng nhớ khối xếp JNI) trong Trình phân tích bộ nhớ để duyệt xem tất cả các lượt tham chiếu JNI cục bộ rồi lọc chúng theo loại Java và ngăn xếp lệnh gọi gốc. Với thông tin này, bạn có thể xem thời điểm cũng như nơi tạo và xoá tham chiếu JNI toàn cục.

Khi ứng dụng của bạn đang chạy, hãy chọn một phần tiến trình mà bạn muốn kiểm tra rồi chọn JNI heap (vùng nhớ khối xếp JNI) trong trình đơn thả xuống phía trên danh sách lớp. Sau đó, bạn có thể kiểm tra các đối tượng trong vùng nhớ khối xếp như bình thường rồi nhấp đúp vào các đối tượng trong thẻ Allocation Call Stack (Ngăn xếp lệnh gọi phân bổ) để xem nơi các lượt tham chiếu JNI được phân bổ và giải phóng trong mã của bạn, minh hoạ trong hình 4.

Hình 4. Xem các lượt tham chiếu JNI toàn cục

Để kiểm tra mức phân bổ bộ nhớ cho mã JNI của ứng dụng, bạn phải triển khai ứng dụng của mình trên một thiết bị chạy Android 8.0 trở lên.

Để biết thêm thông tin về JNI, hãy xem nội dung các mẹo về JNI.

Trình phân tích bộ nhớ cho mã gốc

Trong Trình phân tích bộ nhớ của Android Studio có một Trình phân tích bộ nhớ cho mã gốc (Native Memory Profiler) cho các ứng dụng được triển khai trên thiết bị thực chạy Android 10. Các thiết bị Android 11 hiện được hỗ trợ trong bản thử nghiệm Android Studio 4.2.

Trình phân tích bộ nhớ gốc theo dõi quá trình phân bổ/giải phóng các đối tượng trong mã gốc theo một khoảng thời gian cụ thể rồi đưa ra các thông tin sau:

  • Allocations (Phân bổ): Số lượng đối tượng được phân bổ qua malloc() hoặc toán tử new trong khoảng thời gian đã chọn.
  • Deallocations (Giải phóng): Số lượng đối tượng được giải phóng qua free() hoặc toán tử delete trong khoảng thời gian đã chọn.
  • Allocations Size (Kích thước phân bổ): Kích thước tổng hợp tính bằng byte của mọi lượt phân bổ trong khoảng thời gian đã chọn.
  • Deallocations Size (Kích thước giải phóng): Kích thước tổng hợp tính bằng byte của mọi mức bộ nhớ đã được giải phóng trong khoảng thời gian đã chọn.
  • Total Count (Tổng số): Giá trị trong cột Allocations (Phân bổ) trừ đi giá trị trong cột Deallocations (Giải phóng).
  • Remaining Size: (Kích thước còn lại) Giá trị trong cột Allocations Size (Kích thước phân bổ) trừ đi giá trị trong cột Deallocations Size (Kích thước giải phóng).

Trình phân tích bộ nhớ cho mã gốc

Để ghi lại các lượt phân bổ mã gốc trên thiết bị chạy Android 10 trở lên, hãy chọn Record native allocations (Ghi mức phân bổ cho mã gốc), sau đó chọn Record (Ghi). Quá trình ghi sẽ tiếp tục cho đến khi bạn nhấp vào Stop (Dừng) , sau đó giao diện người dùng Trình phân tích bộ nhớ sẽ chuyển đổi sang một màn hình riêng cho thấy bản ghi mã gốc.

Nút ghi mức phân bổ cho mã gốc

Trên Android 9 trở xuống, bạn không sử dụng được tuỳ chọn Record native allocations (Ghi mức phân bổ cho mã gốc).

Theo mặc định, Trình phân tích bộ nhớ cho mã gốc sử dụng kích thước mẫu là 32 byte: mỗi khi phân bổ được 32 byte bộ nhớ, hệ thống thực hiện một lần chụp tổng quan nhanh bộ nhớ. Kích thước mẫu nhỏ hơn giúp việc chụp thông tin tổng quan nhanh xảy ra thường xuyên hơn, mang lại nhiều dữ liệu chính xác hơn về mức sử dụng bộ nhớ. Kích thước mẫu lớn hơn khiến giảm độ chính xác của dữ liệu, nhưng sẽ tốn ít tài nguyên hơn trên hệ thống và cải thiện hiệu suất trong quá trình ghi.

Để thay đổi kích thước mẫu của Trình phân tích bộ nhớ cho mã gốc:

  1. Chọn Run > Edit Configuration (Chạy > Chỉnh sửa cấu hình).
  2. Chọn mô-đun ứng dụng của bạn trong ngăn bên trái.
  3. Nhấp vào thẻ Profiling (Phân tích), rồi nhập kích thước mẫu trong trường có gắn nhãn Native memory sampling interval (bytes) (Chu kỳ lấy mẫu bộ nhớ mã gốc (byte)).
  4. Xây dựng rồi chạy lại ứng dụng của bạn.

Ghi tệp báo lỗi

Tệp báo lỗi cho biết những đối tượng trong ứng dụng của bạn đang sử dụng bộ nhớ tại thời điểm bạn thu thập dữ liệu cho tệp báo lỗi. Đặc biệt là sau một phiên đăng nhập kéo dài của người dùng, tệp báo lỗi có thể giúp xác định tình trạng rò rỉ bộ nhớ bằng cách cho thấy các đối tượng vẫn còn trong bộ nhớ mà đáng nhẽ không nên ở đó nữa.

Sau khi ghi tệp báo lỗi, bạn có thể xem những thông tin sau:

  • Loại đối tượng mà ứng dụng của bạn đã phân bổ cũng như số lượng mỗi loại.
  • Dung lượng bộ nhớ mà mỗi đối tượng đang sử dụng.
  • Nơi giữ các lượt tham chiếu đến từng đối tượng trong mã của bạn.
  • Ngăn xếp lệnh gọi cho nơi đối tượng được phân bổ. (Hiện nay, tệp báo lỗi chỉ ghi ngăn xếp lệnh gọi trên Android 7.1 trở xuống khi bạn ghi tệp báo lỗi trong khi ghi quá trình phân bổ.)

Để ghi tệp báo lỗi, hãy nhấp vào Capture heap dump (Ghi tệp báo lỗi), sau đó chọn Record (Ghi). Trong khi kết xuất vùng nhớ khối xếp, mức sử dụng bộ nhớ Java có thể tạm thời tăng lên. Hiện tượng này là bình thường vì tệp báo lỗi và ứng dụng của bạn diễn ra trong cùng một quy trình với ứng dụng, đồng thời cần có một lượng bộ nhớ để thu thập dữ liệu.

Sau khi trình phân tích tài nguyên hoàn tất việc ghi tệp báo lỗi, giao diện người dùng của Trình phân tích bộ nhớ sẽ chuyển đổi sang một màn hình riêng biệt cho thấy tệp báo lỗi.

Hình 5. Xem tệp báo lỗi.

Nếu cần thông tin chính xác hơn về thời điểm tạo tệp kết xuất, bạn có thể tạo tệp báo lỗi tại điểm tới hạn trong mã ứng dụng bằng cách gọi dumpHprofData().

Trong danh sách lớp bạn có thể xem những thông tin sau:

  • Allocations (Phân bổ): Số lượt phân bổ trong vùng nhớ khối xếp.
  • Native Size (Kích thước gốc): Tổng dung lượng bộ nhớ gốc mà loại đối tượng này sử dụng (tính bằng byte). Cột này chỉ xuất hiện trên Android 7.0 trở lên.

    Bạn sẽ thấy bộ nhớ ở đây cho một số đối tượng được phân bổ trong Java vì Android sử dụng bộ nhớ gốc cho một số lớp khung (chẳng hạn như Bitmap).

  • Shallow Size (Kích thước đối tượng): Tổng dung lượng bộ nhớ Java mà loại đối tượng này sử dụng (tính bằng byte).

  • Retained Size (Kích thước giữ lại): Tổng dung lượng bộ nhớ được giữ lại do tất cả các thực thể của lớp này (tính bằng byte).

Bạn có thể sử dụng hai trình đơn phía trên danh sách đối tượng được phân bổ để chọn tệp báo lỗi cần kiểm tra cũng như cách sắp xếp dữ liệu.

Trong trình đơn bên trái, hãy chọn vùng nhớ khối xếp cần kiểm tra:

  • default heap (vùng nhớ khối xếp mặc định): Khi hệ thống không chỉ định vùng nhớ khối xếp nào.
  • app heap (vùng nhớ khối xếp ứng dụng): Vùng nhớ khối xếp chính nơi ứng dụng của bạn phân bổ bộ nhớ.
  • image heap (vùng nhớ khối xếp hình ảnh): Hình ảnh khởi động của hệ thống, chứa các lớp được tải trước trong thời gian khởi động. Mức phân bổ tại đây được đảm bảo sẽ không bao giờ di chuyển hoặc biến mất.
  • zygote heap (bộ nhớ khối xếp zygote): Vùng nhớ khối xếp dạng ngầm sao chép (copy-on-write) nơi quy trình ứng dụng được phát triển nhánh qua hệ thống Android.

Trong trình đơn bên phải, hãy chọn cách sắp xếp các lượt phân bổ:

  • Arrange by class (Sắp xếp theo lớp): Nhóm tất cả lượt phân bổ dựa trên tên lớp. Đây là tuỳ chọn mặc định
  • Arrange by package (Sắp xếp theo gói): Nhóm tất cả lượt phân bổ dựa trên tên gói.
  • Arrange by callstack (Sắp xếp theo ngăn xếp lệnh gọi): Nhóm tất cả lượt phân bổ vào ngăn xếp lệnh gọi tương ứng. Tuỳ chọn này chỉ hoạt động nếu bạn ghi tệp báo lỗi trong khi ghi quá trình phân bổ. Mặc dù vậy, có thể có một số đối tượng trong vùng nhớ khối xếp đã được phân bổ trước khi bạn bắt đầu ghi, vì vậy những lượt phân bổ này sẽ xuất hiện trước (liệt kê đơn giản theo tên lớp).

Danh sách này được sắp xếp theo cột Retained Size (Kích thước giữ lại) theo mặc định. Để sắp xếp theo các giá trị trong một cột khác, hãy nhấp vào tiêu đề của cột đó.

Nhấp vào tên lớp để mở cửa sổ Instance View (Chế độ xem thực thể) ở bên phải (như minh hoạ trong hình 6). Mỗi thực thể được liệt kê lại có những thông tin sau đây:

  • Depth (Chiều sâu): Số bước nhảy ngắn nhất từ gốc GC bất kỳ đến thực thể đã chọn.
  • Native Size (Kích thước gốc): Kích thước của thực thể này trong bộ nhớ gốc. Cột này chỉ xuất hiện trên Android 7.0 trở lên.
  • Shallow Size (Kích thước của đối tượng): Kích thước của thực thể này trong bộ nhớ Java.
  • Retained Size (Kích thước giữ lại): Kích thước của bộ nhớ mà thực thể này chiếm ưu thế (theo sơ đồ ưu thế (dominator tree)).

Hình 6. Thời gian cần thiết để ghi một tệp báo lỗi được chỉ định trong tiến trình

Để kiểm tra vùng nhớ khối xếp, hãy làm theo các bước sau:

  1. Duyệt xem danh sách để tìm những đối tượng có số lượng vùng nhớ khối xếp lớn bất thường và có thể bị rò rỉ. Để tìm các lớp đã xác định, hãy nhấp vào tiêu đề cột Class Name (Tên lớp) để sắp xếp theo thứ tự bảng chữ cái. Sau đó, hãy nhấp vào một tên lớp. Ngăn Instance View (Chế độ xem thực thể) xuất hiện ở bên phải cho thấy từng thực thể của lớp đó, như thể hiện trong hình 6.
    • Ngoài ra, bạn có thể tìm nhanh các đối tượng bằng cách nhấp vào biểu tượng Filter (Bộ lọc) hoặc bằng cách nhấn Control+F (Command+F trên Mac) rồi nhập tên lớp hoặc gói trong trường tìm kiếm. Bạn cũng có thể tìm kiếm theo tên phương thức nếu chọn Arrange by callstack (Sắp xếp theo ngăn xếp lệnh gọi) trong trình đơn thả xuống. Nếu bạn muốn sử dụng biểu thức chính quy, hãy đánh dấu hộp bên cạnh Regex (Biểu thức chính quy). Đánh dấu vào hộp bên cạnh Match case (Khớp chữ hoa chữ thường) nếu cụm từ tìm kiếm của bạn có phân biệt chữ hoa chữ thường.
  2. Trong ngăn Instance View (Chế độ xem thực thể), hãy nhấp vào một thực thể. Thẻ References (Tham chiếu) xuất hiện bên dưới, cho thấy mọi thông tin tham chiếu đến đối tượng đó.

    Hoặc nhấp vào mũi tên bên cạnh tên thực thể để xem tất cả trường của thực thể, sau đó nhấp vào tên trường để xem tất cả lượt tham chiếu. Nếu bạn muốn xem thông tin chi tiết về thực thể cho một trường, hãy nhấp chuột phải vào trường đó rồi chọn Go to Instance (Chuyển đến thực thể).

  3. Trong thẻ References (Tham chiếu), nếu bạn xác định được một tệp đối chiếu có thể bị rò rỉ bộ nhớ, hãy nhấp chuột phải vào tệp đối chiếu đó rồi chọn Go to Instance (Chuyển đến thực thể). Thao tác này sẽ chọn thực thể tương ứng trong tệp báo lỗi, cho bạn thấy dữ liệu về thực thể có trong tệp đó.

Trong tệp báo lỗi của bạn, hãy tìm lỗi rò rỉ bộ nhớ do nguyên nhân bất kỳ trong số sau:

  • Các lượt tham chiếu kéo dài đến Activity, Context, View, Drawable và các đối tượng khác có thể duy trì tham chiếu đến vùng chứa Activity hoặc Context.
  • Các lớp không tĩnh bên trong (chẳng hạn như Runnable) có thể duy trì một thực thể Activity.
  • Bộ nhớ đệm duy trì các đối tượng lâu hơn mức cần thiết.

Lưu tệp báo lỗi dưới dạng tệp HPROF

Sau khi ghi lại tệp báo lỗi, bạn chỉ có thể xem dữ liệu trong Trình phân tích bộ nhớ trong khi trình phân tích này đang chạy. Khi thoát khỏi phiên phân tích bạn sẽ mất tệp báo lỗi. Vì vậy, nếu bạn muốn lưu tệp để xem xét sau, hãy xuất tệp báo lỗi sang định dạng HPROF. Trong Android Studio phiên bản 3.1 trở xuống, nút Export capture to file (Xuất bản ghi vào tệp) nằm ở bên trái thanh công cụ bên dưới tiến trình; trong Android Studio 3.2 trở lên, có một nút Export Heap Dump (Xuất tệp báo lỗi) ở bên phải mỗi mục Heap Dump (Tệp báo lỗi) trong ngăn Sessions (Phiên). Trong hộp thoại Export as (Xuất dưới dạng), hãy lưu theo phần mở rộng tên tệp .hprof.

Để sử dụng một trình phân tích HPROF khác (chẳng hạn như jhat, bạn cần chuyển đổi tệp HPROF từ định dạng Android sang định dạng Java SE HPROF. Bạn có thể thực hiện việc này bằng công cụ hprof-conv có trong thư mục android_sdk/platform-tools/. Chạy lệnh hprof-conv với hai đối số: tệp HPROF ban đầu và vị trí để viết tệp HPROF đã chuyển đổi. Ví dụ:

hprof-conv heap-original.hprof heap-converted.hprof

Nhập tệp báo lỗi

Để nhập tệp HPROF (.hprof), hãy nhấp vào biểu tượng Start a new profiling session (Bắt đầu một phiên phân tích mới) trong ngăn Session (Phiên), chọn Load from file (Tải qua tệp) rồi chọn tệp trên trình duyệt tệp.

Bạn cũng có thể nhập tệp HPROF bằng cách kéo tệp từ trình duyệt tệp vào cửa sổ trình chỉnh sửa.

Phát hiện rò rỉ trong Trình phân tích bộ nhớ

Khi phân tích một tệp báo lỗi trong Trình phân tích bộ nhớ, bạn có thể lọc dữ liệu phân tích mà Android Studio cho rằng có thể chỉ báo lỗi rò rỉ bộ nhớ cho các thực thể ActivityFragment trong ứng dụng của bạn.

Bộ lọc này cho thấy những loại dữ liệu sau đây:

  • Các thực thể Activity đã bị huỷ bỏ nhưng vẫn đang được tham chiếu.
  • Các thực thể Fragment không có FragmentManager hợp lệ nhưng vẫn đang được tham chiếu.

Trong một số trường hợp, chẳng hạn như sau, bộ lọc có thể tạo ra các kết quả dương tính giả:

  • Fragment được tạo nhưng chưa được sử dụng.
  • Fragment đang được lưu vào bộ nhớ đệm nhưng không thuộc FragmentTransaction.

Để sử dụng tính năng này, trước tiên, hãy ghi lại tệp báo lỗi hoặc nhập tệp báo lỗi vào Android Studio. Để xem các phân đoạn và hoạt động có thể bị rò rỉ bộ nhớ, hãy đánh dấu vào hộp Activity/Fragment Leaks (Rò rỉ phân mảnh/hoạt động) trong ngăn tệp báo lỗi của Trình phân tích bộ nhớ, như minh hoạ trong hình 7.

Trình phân tích: Phát hiện rò rỉ bộ nhớ

Hình 7. Lọc tệp báo lỗi để xem tìm lỗi rò rỉ bộ nhớ.

Kỹ thuật phân tích bộ nhớ

Trong khi sử dụng Trình phân tích bộ nhớ, bạn nên tạo áp lực cho mã nguồn ứng dụng và buộc rò rỉ bộ nhớ. Có một cách để tìm ra lỗi rò rỉ bộ nhớ trong ứng dụng là cho ứng dụng chạy một lúc trước khi kiểm tra vùng nhớ khối xếp. Các lỗi rò rỉ có thể dần lộ ra trên quá trình phân bổ trong vùng nhớ khối xếp. Tuy nhiên, rò rỉ càng ít thì bạn cần chạy ứng dụng càng lâu để thấy được nơi rò rỉ.

Bạn cũng có thể kích hoạt sự cố rò rỉ bộ nhớ theo một trong các cách sau:

  • Liên tục xoay thiết bị từ chế độ dọc sang chế độ ngang rồi ngược lại trong khi ở nhiều trạng thái hoạt động. Việc xoay thiết bị thường có thể khiến ứng dụng rò rỉ đối tượng Activity, Context hoặc View do hệ thống tái tạo Activity và nếu ứng dụng của bạn tham chiếu đến một trong những đối tượng đó ở nơi khác, thì hệ thống không thu thập được rác của đối tượng đó.
  • Chuyển đổi giữa ứng dụng của bạn với một ứng dụng khác khi ở nhiều trạng thái hoạt động (chuyển đến Màn hình chính, sau đó quay lại ứng dụng của bạn).

Mẹo: Bạn cũng có thể thực hiện các bước trên bằng cách sử dụng khung kiểm thử monkeyrunner.