Giám sát mức sử dụng bộ nhớ

Để tối ưu hoá hiệu quả mức sử dụng bộ nhớ của trò chơi, trước tiên, bạn phải hiểu cách nền tảng Android đo lường bộ nhớ và cách sử dụng phép đo từ xa hệ thống, API chẩn đoán và công cụ phân tích. Hướng dẫn này trình bày chi tiết cách giám sát, ghi lại và phân tích việc phân bổ bộ nhớ của trò chơi theo các nguyên tắc mới của nền tảng.

Tìm hiểu về RSS và các chỉ số về việc hoán đổi

Để phân tích và gỡ lỗi hiệu quả hành vi bộ nhớ của trò chơi, bạn phải hiểu rõ các chỉ số kỹ thuật chính xác mà nền tảng Android dùng để thực thi bộ nhớ. Để biết thông tin chi tiết về cách tham số đo từ xa này được xử lý và theo dõi trong thực tế, hãy xem tài liệu Android Vitals – Mức sử dụng bộ nhớ (RSS ẩn danh + bộ nhớ ảo).

1. RSS ẩn danh (RssAnon)

Kích thước cài đặt thường trú (RSS) đo lường phần bộ nhớ mà một quy trình chiếm dụng và được lưu giữ trong RAM vật lý của thiết bị. RSS được chia thành bộ nhớ được hỗ trợ bằng tệp và bộ nhớ ẩn danh. Chỉ số thực thi của Android chỉ tập trung vào RSS ẩn danh:

  • Nội dung: Các trang bộ nhớ do quy trình trò chơi của bạn phân bổ trực tiếp và không được liên kết với một tệp thực trên bộ nhớ. Các trang này bao gồm các vùng nhớ heap Java hoặc Kotlin, ngăn xếp thực thi luồng và quan trọng nhất là các hoạt động phân bổ bộ nhớ gốc (chẳng hạn như các trình phân bổ công cụ C++ tuỳ chỉnh hoặc các khối bộ nhớ được yêu cầu bằng malloc gốc hoặc mới và bị làm hỏng bởi logic trò chơi). Tìm hiểu thêm về chỉ số này trong từ điển Bộ nhớ xử lý (RSS).
  • Tại sao điều này lại quan trọng: Các công cụ phát triển trò chơi sử dụng các nhóm bộ nhớ gốc lớn để xử lý vật lý, kết xuất và logic. Vì các nhóm này không được sao lưu bằng tệp, nên chúng hoàn toàn nằm trong RSS ẩn danh và chiếm phần lớn dung lượng vật lý của trò chơi.

2. Hoán đổi không nén (VmSwap)

Android không hỗ trợ không gian trao đổi dựa trên đĩa truyền thống do các hạn chế về độ trễ và hao mòn bộ nhớ flash. Thay vào đó, hệ thống sẽ sử dụng zRAM (Hoán đổi không nén):

  • Nội dung: Khi áp lực RAM thực tăng lên, trình nền quản lý bộ nhớ của nhân sẽ nén các trang ẩn danh không hoạt động và di chuyển chúng vào một phần RAM thực chuyên dụng, không nén (zRAM).
  • Cách tính chỉ số: Hệ thống theo dõi chỉ số này dựa trên kích thước chưa nén (VmSwap) để đánh giá nhu cầu thực tế về bộ nhớ thực của trò chơi. Nếu trò chơi của bạn phân bổ bộ nhớ và hệ thống hoán đổi bộ nhớ đó sang zRAM, thì bộ nhớ đó vẫn được tính vào Tổng mức sử dụng bộ nhớ của trò chơi.

3. Trạng thái xử lý

Mức sử dụng bộ nhớ được chia nhỏ theo trạng thái quy trình trong Android Vitals. Đối với nhà phát triển trò chơi, các trò chơi hoặc SDK bên thứ ba cũng có thể kích hoạt các dịch vụ do người dùng cảm nhận hoặc dịch vụ nền một cách bất ngờ.

  • Nội dung: Nền trước, Dịch vụ có thể nhận biết, Nền sau và Nội dung được lưu vào bộ nhớ đệm.
  • Tại sao điều này lại quan trọng: Mỗi trạng thái quy trình sẽ có tác động khác nhau đến hoạt động quản lý bộ nhớ của hệ điều hành Android. Bạn có thể không biết trò chơi của mình đang chạy ở trạng thái quy trình nhạy cảm nếu bất kỳ SDK nào của bên thứ ba vô tình kích hoạt một tác vụ ở chế độ nền. Theo dõi xem trò chơi của bạn có đang chạy trong nền hay không bằng cách sử dụng RunningAppProcessInfo.

Giao diện lập trình ứng dụng (API)

Android cung cấp các API hệ thống cho phép trò chơi của bạn phản hồi linh hoạt với áp lực bộ nhớ và ghi lại thông tin chẩn đoán bộ nhớ chi tiết trong thời gian chạy.

Phản hồi các sự kiện cắt bớt bộ nhớ

Hệ thống sử dụng onTrimMemory để thông báo cho ứng dụng của bạn về các sự kiện trong vòng đời. Đây là cơ hội tốt để ứng dụng của bạn tự nguyện giảm mức sử dụng bộ nhớ và tránh bị low-memory killer (LMK) tắt để giải phóng bộ nhớ cho các ứng dụng khác sử dụng.

Nếu hệ thống huỷ ứng dụng của bạn ở chế độ nền, thì người dùng sẽ gặp phải tình trạng khởi động nguội chậm khi tiếp tục. Việc giảm mức sử dụng bộ nhớ nền sẽ giúp ngăn chặn các trường hợp chấm dứt nền này.

Khi phản hồi các sự kiện cắt bớt, hãy giải phóng các hoạt động phân bổ bộ nhớ lớn, có thể xây dựng lại mà không cần thiết ngay lập tức:

  • Ví dụ: Cắt bớt hoặc xoá các bitmap được lưu vào bộ nhớ đệm (được giải mã từ bộ nhớ cục bộ) để phản hồi TRIM_MEMORY_UI_HIDDEN.

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }
        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
                // Release memory related to UI elements, such as bitmap caches.
            }
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                // Release memory related to background processing, such as by
                // closing a database connection.
            }
        }
    }
}

ProfilingManager

Được giới thiệu trong Android 15 (cấp độ API 35), API ProfilingManager cho phép các ứng dụng chụp ảnh nhanh được xác định theo chương trình (chẳng hạn như hồ sơ ảnh chụp nhanh của vùng nhớ khối xếp, theo dõi hệ thống và tệp báo lỗi Java) ngay trong thời gian chạy.

Nhà phát triển có thể kích hoạt tính năng chụp theo cách thủ công tại các cảnh cụ thể hoặc đăng ký các điều kiện kích hoạt tự động như TRIGGER_TYPE_ANOMALY để tự động kích hoạt tính năng chụp khi quy trình trò chơi vi phạm ngưỡng của Bộ giới hạn bộ nhớ. Tuy nhiên, nhà phát triển trò chơi phải tính đến những hạn chế quan trọng trong các công cụ phát triển trò chơi hiện đại:

Lưu ý: Các công cụ phát triển trò chơi hiện đại (chẳng hạn như Unity hoặc Unreal) quản lý hiệu suất thực thi bằng cách phân bổ trước các khối bộ nhớ ảo lớn từ nhân bằng cách sử dụng mmap với cờ MAP_ANONYMOUS. Sau đó, các công cụ này sẽ sử dụng các trình phân bổ phụ tuỳ chỉnh (ví dụ: trình quản lý bộ nhớ gốc của Unity hoặc BinnedAllocators của Unreal) để chia nhỏ và phân bổ các khối bộ nhớ nội bộ.

ApplicationExitInfo

Nếu trò chơi của bạn bị chấm dứt ở chế độ nền hoặc bị tắt do vi phạm giới hạn bộ nhớ của từng quy trình, thì các cơ chế tệp kết xuất bộ nhớ gốc hoặc Java tiêu chuẩn (chẳng hạn như Firebase Crashlytics) sẽ không đăng ký sự kiện này. Để truy vấn và ghi lại các lần kết thúc này theo cách lập trình, nhà phát triển nên tận dụng API ApplicationExitInfo khi khởi động trò chơi.

  • Triển khai: Khi bắt đầu, hãy gọi ActivityManager.getHistoricalProcessExitReasons() để truy xuất lý do thoát của các phiên gần đây.
  • Các lý do chính khiến bộ nhớ bị đầy:
    • REASON_LOW_MEMORY: Cho biết quy trình đã bị Low Memory Killer (LMK) của hệ thống chấm dứt. Quá trình chấm dứt này xảy ra khi áp lực bộ nhớ trên toàn thiết bị ở mức cao và hệ điều hành phải thu hồi RAM. Lý do thoát này cho biết dấu vết nền của trò chơi quá lớn để cùng tồn tại với các ứng dụng khác.
    • REASON_MEMORY_LIMITER (Android 17 (cấp độ API 37) trở lên): Cho biết quy trình đã bị tắt cụ thể vì vượt quá giới hạn bộ nhớ cgroup (RssAnon + VmSwap) do Trình giới hạn bộ nhớ của nền tảng chỉ định. Việc chấm dứt này có thể xảy ra ngay cả khi thiết bị còn đủ bộ nhớ vật lý, cho thấy hành vi vi phạm trực tiếp giới hạn của từng quy trình.

Sử dụng các công cụ có sẵn

Hãy sử dụng các công cụ nền tảng sau đây trong quá trình phát triển và kiểm thử chất lượng để đo lường chính xác mức sử dụng bộ nhớ của trò chơi.

meminfo

Công cụ này thu thập số liệu thống kê về bộ nhớ để cho biết mức bộ nhớ PSS đã được phân bổ và các danh mục được sử dụng bộ nhớ đó.

In số liệu thống kê meminfo theo một trong các cách sau:

  • Sử dụng lệnh adb shell dumpsys meminfo package-name.
  • Sử dụng lệnh gọi MemoryInfo qua Android Debug API.

Số liệu thống kê PrivateDirty cho biết dung lượng RAM trong tiến trình không thể phân trang vào ổ đĩa và không được chia sẻ với bất kỳ tiến trình nào khác. Phần lớn dung lượng này sẽ được cung cấp cho hệ thống khi quy trình đó bị tắt.

Các điểm theo dõi bộ nhớ

Các điểm theo dõi bộ nhớ sẽ theo dõi lượng bộ nhớ RSS mà trò chơi đang sử dụng. Việc tính toán mức sử dụng bộ nhớ RSS nhanh hơn nhiều so với tính mức sử dụng PSS. Vì tốc độ tính nhanh hơn nên RSS cho thấy rõ hơn về những thay đổi trong kích thước bộ nhớ để đo lường chính xác hơn mức sử dụng bộ nhớ cao nhất. Do đó, bạn sẽ dễ dàng nhận thấy các lúc cao điểm có thể khiến trò chơi hết bộ nhớ.

Perfetto

Perfetto là một bộ công cụ giúp thu thập thông tin về hiệu suất và bộ nhớ trên thiết bị rồi hiển thị thông tin đó trong một giao diện người dùng dựa trên nền tảng web. Công cụ này hỗ trợ các dấu vết (trace) dài tuỳ ý để bạn có thể xem cách RSS thay đổi theo thời gian. Bạn cũng có thể đưa ra các truy vấn SQL trên dữ liệu mà công cụ này tạo ra để xử lý ngoại tuyến. Bật các dấu vết dài qua ứng dụng Theo dõi hệ thống. Hãy đảm bảo rằng bạn đã bật danh mục memory:Memory để theo dõi dấu vết. Đối với hoạt động đo lường bộ nhớ tuỳ chỉnh trong quá trình phát triển và thử nghiệm, bạn cũng có thể sử dụng API heapprofd (Beta).

Kiểm tra RssAnon và hoán đổi trong Perfetto

Để kiểm tra tác động của bộ nhớ ẩn danh và hoạt động hoán đổi zRAM của trò chơi, hãy tải tệp dấu vết lên giao diện người dùng dựa trên web tại ui.perfetto.dev và làm theo các kỹ thuật phân tích này, được thiết kế cho các nghiên cứu chuyên sâu về bộ nhớ (xem Nghiên cứu điển hình về phân tích bộ nhớ Perfetto để biết thêm thông tin):

1. Trực quan hoá bộ đếm bộ nhớ trên Dòng thời gian

  • Xác định vị trí quy trình: Trong danh sách điều hướng, hãy tìm tên gói hoặc tên quy trình của trò chơi.
  • Mở rộng nhóm theo dõi: Nhấp vào hàng quy trình để mở rộng các bản theo dõi luồng và xác định vị trí nhóm con có tên là Bộ nhớ.
  • Phân tích các bản nhạc:
    • mem.rss.anon (RSS ẩn danh): Biểu đồ đường này cho thấy RAM vật lý theo thời gian thực mà các nhóm bộ nhớ không được quản lý của trò chơi chiếm giữ. Theo dõi dòng thời gian này trong quá trình tải cảnh, cửa sổ bật lên trên giao diện người dùng hoặc chuyển đổi lối chơi để kiểm tra các đỉnh phân bổ cao.
    • mem.swap (Compressed Swap hoặc VmSwap): Biểu đồ này vẽ kích thước đã nén trước của các khối bộ nhớ được di chuyển sang zRAM. Hoạt động trao đổi cao trùng với quá trình chơi trò chơi cho biết trò chơi của bạn đang chạy trên một thiết bị có bộ nhớ bị hạn chế và hệ thống đang chủ động nén các tài sản nền.

2. Chạy truy vấn SQL (Trình xử lý dấu vết) Để phân tích chi tiết ngoại tuyến, bạn có thể thực thi các truy vấn SQL ngay trong bảng điều khiển giao diện người dùng Perfetto hoặc sử dụng thư viện Python Trình xử lý dấu vết độc lập để tính toán các đỉnh thống kê.

  • Tìm mức phân bổ RSS ẩn danh cao nhất:

    SELECT
      max(value) / 1024 / 1024 AS max_rss_anon_mb
    FROM counter
    JOIN counter_track ON counter.track_id = counter_track.id
    WHERE counter_track.name = 'mem.rss.anon'
      AND counter_track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      );
    
  • Tương quan RssAnon và VmSwap tại một dấu thời gian bất kỳ:

    SELECT
      ts,
      track.name AS metric_type,
      value / 1024 / 1024 AS size_mb
    FROM counter
    JOIN counter_track track ON counter.track_id = track.id
    WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap')
      AND track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      )
    ORDER BY ts ASC;
    

Để biết thêm thông tin chi tiết về cách kiểm tra tệp dấu vết bằng Android Studio, hãy xem phần Kiểm tra dấu vết hệ thống: Bộ nhớ xử lý (RSS). Để biết thông tin chi tiết về việc tạo tập lệnh cho hồ sơ bộ nhớ, hãy xem phần Ghi lại các hoạt động phân bổ gốc.

heapprofd

heapprofd là một công cụ theo dõi bộ nhớ thuộc Perfetto. Công cụ này có thể giúp bạn tìm rò rỉ bộ nhớ bằng cách cho biết nơi bộ nhớ được phân bổ bằng malloc. Bạn có thể bắt đầu heapprofd bằng cách sử dụng tập lệnh Python. Vì công cụ này có mức hao tổn thấp nên sẽ không ảnh hưởng đến hiệu suất như các công cụ khác (ví dụ: Malloc Debug).

bugreport

bugreport là một công cụ ghi nhật ký để tìm hiểu xem trò chơi có gặp sự cố do hết bộ nhớ hay không. Kết quả mà công cụ xuất ra chi tiết hơn nhiều so với sử dụng logcat. Công cụ này hữu ích cho việc gỡ lỗi bộ nhớ vì nó cho biết rõ rằng trò chơi có gặp sự cố do hết bộ nhớ hoặc bị LMK tắt hay không.

Để biết thêm thông tin, hãy xem bài viết Ghi lại và đọc báo cáo lỗi.

Công cụ phát triển trò chơi

Mặc dù nhật ký cấp nền tảng và số liệu đo từ xa của hệ thống là rất quan trọng để theo dõi ngưỡng và mức độ tuân thủ của hệ điều hành, nhưng các công cụ dành riêng cho công cụ trò chơi sẽ giúp bạn phân bổ trực tiếp các mục tiêu trở lại các đối tượng trò chơi, hành vi kịch bản và hệ thống phân cấp cảnh đang hoạt động.

Unity

Trong môi trường Unity Engine, bạn có thể ước tính chính xác mức sử dụng bộ nhớ RSS ẩn danh + Swap của Android trong thời gian chạy với độ tin cậy cao (thường hiển thị mức chênh lệch dưới 10% so với các giá trị thực ở cấp hệ điều hành) bằng cách sử dụng các lớp và công cụ lập hồ sơ gốc của Unity.

Để xem hướng dẫn đầy đủ từng bước, bao gồm cả các quy tắc định cấu hình và tập lệnh thời gian chạy, hãy xem bài viết Cách kiểm tra bộ nhớ bằng các công cụ của Unity.

  • API trình phân tích tài nguyên Unity: Bạn có thể ước chừng mức sử dụng bộ nhớ không được quản lý của trò chơi theo cách lập trình trong thời gian chạy bằng cách truy vấn các chỉ số công cụ cốt lõi:
    • Sử dụng lớp Trình phân tích tài nguyên: Theo dõi tổng số lượt phân bổ bộ nhớ bằng cách cộng các giá trị của Profiler.GetTotalReservedMemoryLong()Profiler.GetMonoHeapSizeLong().
    • Sử dụng lớp ProfilerRecorder: Theo dõi các danh mục bộ nhớ một cách linh động. Để thiết lập một giá trị cơ sở gần đúng đáng tin cậy, hãy tìm nạp Tổng bộ nhớ dự phòng (trên bản dựng Phát hành) hoặc trừ Bộ nhớ dự phòng Gfx (trên bản dựng Phát triển) để loại bỏ các thành phần bộ nhớ đồ hoạ dựa trên tệp.
  • Trình phân tích bộ nhớ Unity: Để xác định và gỡ lỗi tình trạng rò rỉ bộ nhớ khi không kết nối mạng, hãy chụp nhanh bộ nhớ và kiểm tra biểu đồ Bộ nhớ thường trú trên thiết bị trong phần Tất cả bộ nhớ. Để tính toán kích thước gần đúng, hãy cộng tổng của các danh mục sau: Untracked, Android Runtime, Native và Managed.
    • Hạn chế của zRAM: Trong điều kiện bộ nhớ hạn chế, nhân Android có thể nén các trang bộ nhớ không hoạt động vào không gian hoán đổi (zRAM). Vì Trình phân tích bộ nhớ Unity không thể phát hiện các tham số trao đổi ở cấp hệ điều hành, nên bạn có thể thấy sự khác biệt nhỏ về mức sử dụng bộ nhớ trong các cảnh có mức sử dụng bộ nhớ lớn. Tham chiếu chéo các số liệu ước tính của bạn với Perfetto để xác nhận các giá trị chính xác.