Phân bổ bộ nhớ giữa các quy trình

Nền tảng Android hoạt động trên cơ sở bộ nhớ trống là bộ nhớ bị lãng phí. Hệ thống sẽ luôn cố sử dụng tất cả bộ nhớ có sẵn. Ví dụ: sau khi các ứng dụng đã đóng, hệ thống sẽ vẫn lưu giữ chúng trong bộ nhớ để người dùng có thể nhanh chóng chuyển về ứng dụng khi được mở lại. Vì lý do này, các thiết bị Android thường chạy với rất ít bộ nhớ trống. Việc quản lý bộ nhớ là rất quan trọng để phân bổ bộ nhớ đúng cách giữa các quy trình hệ thống quan trọng và nhiều ứng dụng của người dùng.

Trang này thảo luận những thông tin cơ bản về cách Android phân bổ bộ nhớ cho hệ thống và cho các ứng dụng của người dùng. Nó cũng giải thích cách hệ điều hành xử lý các tình huống dung lượng bộ nhớ thấp.

Loại bộ nhớ

Thiết bị Android chứa ba loại bộ nhớ khác nhau: RAM, zRAM và bộ nhớ lưu trữ. Vui lòng lưu ý cả CPU và GPU đều truy cập vào cùng một RAM.

Loại bộ nhớ

Hình 1. Các loại bộ nhớ – RAM, zRAM và bộ nhớ lưu trữ

  • RAM là loại bộ nhớ nhanh nhất, nhưng thường bị giới hạn về dung lượng. Các thiết bị cao cấp thường có dung lượng RAM lớn nhất.

  • zRAM là một phân vùng RAM được dùng để hoán đổi không gian. Mọi thứ đều được nén lại khi đặt vào zRAM, sau đó được giải nén khi sao chép khỏi zRAM. Phân vùng RAM này tăng hoặc giảm dung lượng khi các trang được di chuyển vào hoặc ra khỏi zRAM. Các nhà sản xuất thiết bị có thể đặt kích thước tối đa cho nó.

  • Bộ nhớ lưu trữ chứa tất cả dữ liệu cố định, chẳng hạn như hệ thống tệp và mã đối tượng đi kèm cho tất cả ứng dụng, thư viện và nền tảng. Bộ nhớ lưu trữ có dung lượng lớn hơn nhiều so với 2 loại bộ nhớ còn lại kia. Trên Android, dung lượng lưu trữ không được dùng cho việc hoán đổi không gian như các phương thức triển khai khác trên hệ điều hành Linux, vì việc ghi thường xuyên có thể làm chậm và giảm thời gian sử dụng phương tiện lưu trữ.

Trang bộ nhớ

RAM được chia thành các trang. Thông thường, mỗi trang đều có bộ nhớ là 4KB.

Trang có thể là ở trạng thái trống hoặc đang được sử dụng. Trang trống là phần dung lượng RAM chưa sử dụng. Các trang đang được sử dụng là RAM mà hệ thống đang sử dụng, đồng thời được nhóm thành các danh mục sau:

  • Bộ nhớ đệm: Bộ nhớ do một tệp lưu trữ hỗ trợ (ví dụ: mã hoặc các tệp được liên kết với bộ nhớ). Có 2 loại bộ nhớ đệm:
    • Riêng tư: Thuộc sở hữu của một quy trình và không được chia sẻ
      • Sạch: Bản chưa sửa đổi của tệp trên bộ nhớ lưu trữ, có thể được xóa bằng kswapd để tăng bộ nhớ trống
      • Sửa đổi: Bản sao đã sửa đổi của tệp trên bộ nhớ lưu trữ, có thể được di chuyển hoặc nén vào zRAM bằng kswapd để tăng bộ nhớ trống
    • Chia sẻ: Được sử dụng trong nhiều quy trình
      • Sạch: Bản chưa sửa đổi của tệp trên bộ nhớ lưu trữ, có thể được xóa bằng kswapd để tăng bộ nhớ trống
      • Sửa đổi: Bản sửa đổi của tệp trên bộ nhớ lưu trữ; cho phép các thay đổi được ghi lại vào tệp trong bộ nhớ lưu trữ để tăng bộ nhớ trống bằng kswapd, hoặc sử dụng msync() hay munmap() một cách rõ ràng
  • Ẩn danh: Bộ nhớ không được tệp lưu trữ hỗ trợ (ví dụ: được phân bổ bởi mmap() với cờ MAP_ANONYMOUS được thiết lập)
    • Sửa đổi: Có thể di chuyển/nén trong zRAM bằng kswapd để tăng bộ nhớ trống

Tỷ lệ trang trống và đã sử dụng sẽ thay đổi theo thời gian khi hệ thống chủ động quản lý RAM. Các khái niệm được giới thiệu trong phần này là chìa khóa để quản lý các tình huống dung lượng bộ nhớ thấp. Phần tiếp theo của tài liệu này sẽ mô tả các tình huống này một cách chi tiết hơn.

Quản lý bộ nhớ thấp

Android có hai cơ chế chính để xử lý các tình huống về bộ nhớ thấp: kernel swap daemon (chuyển đổi bộ nhớ đã sử dụng thành bộ nhớ trống) và low-memory killer (đóng các hoạt động để tạo thêm bộ nhớ trống).

kernel swap daemon

Kernel swap daemon (kswapd) là một phần của Linux kernel, nó chuyển đổi bộ nhớ đã sử dụng thành bộ nhớ trống. Daemon (trình nền) sẽ chạy khi bộ nhớ trống trên thiết bị sắp hết. Linux kernel duy trì bộ nhớ trống ở các ngưỡng cao và thấp. Khi bộ nhớ còn trống dưới ngưỡng thấp, kswapd sẽ bắt đầu hoạt động để lấy lại bộ nhớ. Một khi bộ nhớ trống đạt đến ngưỡng cao, kswapd sẽ ngừng việc thu hồi bộ nhớ.

kswapd có thể lấy lại các trang sạch khi xóa các trang đó vì nó có bộ nhớ đệm hỗ trợ và chưa được sửa đổi. Nếu một quy trình cố gắng xử lý một trang sạch đã bị xóa, thì hệ thống sẽ sao chép trang đó từ bộ nhớ lưu trữ sang RAM. Thao tác này được gọi là cách phân trang theo nhu cầu.

Đã xoá trang sạch mà bộ nhớ sao lưu

Hình 2. Đã xoá trang sạch mà bộ nhớ sao lưu

kswapd có thể di chuyển các trang sửa đổi bộ nhớ đệm riêng tư và các trang sửa đổi ẩn danh vào zRAM, nơi chúng được nén. Việc này sẽ giải phóng bộ nhớ hiện có trong RAM (các trang trống). Nếu một quy trình cố tiếp cận một trang sửa đổi trong zRAM, thì trang đó sẽ được giải nén và chuyển trở lại RAM. Nếu quá trình liên kết với một trang nén bị hủy, thì trang đó sẽ bị xóa khỏi zRAM.

Nếu dung lượng bộ nhớ trống giảm xuống dưới một ngưỡng nhất định, hệ thống sẽ bắt đầu đóng các quá trình.

Đã di chuyển trang sửa đổi sang zRAM và nén

Hình 3. Đã di chuyển trang sửa đổi sang zRAM và nén

Mô đun tắt ứng dụng khi bộ nhớ thấp

Có nhiều lúc kswapd không thể giải phóng đủ bộ nhớ cho hệ thống. Trong trường hợp này, hệ thống sẽ sử dụng onTrimMemory() để thông báo cho ứng dụng là bộ nhớ sắp hết và nó sẽ giảm mức phân bổ. Nếu điều này chưa đủ, kernel sẽ bắt đầu hủy các quy trình để giải phóng bộ nhớ. Nó sử dụng low-memory killer (LMK) để làm điều này.

Để quyết định cần loại bỏ quy trình nào, LMK sẽ sử dụng điểm "hết bộ nhớ" được gọi là oom_adj_score để ưu tiên các quy trình đang chạy. Những quy trình có điểm cao sẽ bị xóa trước. Ứng dụng nền thường sẽ bị loại bỏ đầu tiên, còn các quy trình hệ thống sẽ bị loại bỏ sau cùng. Bảng dưới đây liệt kê các danh mục có điểm LMK từ cao đến thấp. Các mục trong danh mục có điểm số cao nhất ở hàng một sẽ bị xóa trước:

Các quy trình trên Android, điểm số cao ở trên cùng

Hình 4. Các quy trình của Android, với điểm cao ở trên cùng và điểm thấp ở dưới cùng

Dưới đây là nội dung mô tả cho các danh mục khác nhau trong bảng trên:

  • Ứng dụng nền: Ứng dụng đã chạy trước đây và hiện không hoạt động. Trước tiên, LMK sẽ loại bỏ các ứng dụng nền, bắt đầu với ứng dụng có oom_adj_score cao nhất.

  • Ứng dụng trước: Ứng dụng nền được sử dụng gần đây nhất. Ứng dụng trước có mức độ ưu tiên cao hơn (điểm thấp hơn) so với các ứng dụng nền, vì nhiều khả năng người dùng sẽ chuyển sang ứng dụng đó hơn là một trong các ứng dụng nền.

  • Ứng dụng trên màn hình chính: Đây là ứng dụng trình chạy. Việc loại bỏ nó sẽ khiến hình nền cũng bị biến mất.

  • Dịch vụ: Dịch vụ bắt đầu bằng các ứng dụng và có thể bao gồm việc đồng bộ hóa hoặc tải lên đám mây.

  • Các ứng dụng dễ nhận biết: người dùng có thể nhận biết các ứng dụng không ở nền trước theo một cách nào đó, chẳng hạn như chạy một quy trình tìm kiếm để hiển thị một giao diện người dùng nhỏ hoặc nghe nhạc.

  • Ứng dụng trên nền trước: Ứng dụng hiện đang được sử dụng. Việc loại bỏ ứng dụng trên nền trước trông giống như một sự cố ứng dụng, có thể cho người dùng biết là thiết bị đang gặp sự cố nào đó.

  • Cố định mang tính liên tục (dịch vụ): Đây là các dịch vụ chính dành cho thiết bị, chẳng hạn như điện thoại và wifi.

  • Hệ thống: Các quy trình của hệ thống. Khi các quy trình này bị hủy, điện thoại có thể khởi động lại.

  • Gốc: Các quy trình ở cấp rất thấp mà hệ thống sử dụng (ví dụ như kswapd).

Các nhà sản xuất thiết bị có thể thay đổi hành vi của LMK.

Đang tính toán mức sử dụng bộ nhớ

Kernel theo dõi tất cả các trang bộ nhớ trong hệ thống.

Các trang được sử dụng theo các quy trình khác nhau

Hình 5. Các trang được sử dụng theo các quy trình khác nhau

Khi xác định dung lượng bộ nhớ mà một ứng dụng đang dùng, hệ thống phải tính đến các trang dùng chung. Các ứng dụng truy cập cùng một dịch vụ hoặc thư viện sẽ được chia sẻ các trang bộ nhớ. Ví dụ như dịch vụ Google Play và ứng dụng trò chơi có thể đang chia sẻ dịch vụ vị trí. Điều này gây khó khăn cho việc xác định dung lượng bộ nhớ mà dịch vụ sử dụng trên từng ứng dụng.

Các trang được chia sẻ bởi hai ứng dụng

Hình 6. Các trang được chia sẻ bởi hai ứng dụng (ở giữa)

Để xác định dấu vết bộ nhớ cho một ứng dụng, bạn có thể sử dụng bất kỳ chỉ số nào dưới đây:

  • Kích thước cài đặt thường trú (RSS): Số trang được chia sẻ và không được chia sẻ mà ứng dụng sử dụng
  • Kích thước cài đặt theo tỷ lệ (PSS): Số trang không được chia sẻ mà ứng dụng dùng và phân phối đồng đều các trang được chia sẻ (ví dụ: nếu 3 quy trình có chung 3MB, thì mỗi quy trình nhận được 1MB trong PSS)
  • Kích thước cài đặt riêng biệt (USS): Số trang không được chia sẻ mà ứng dụng sử dụng (không bao gồm các trang được chia sẻ)

PSS hữu ích cho các hệ điều hành muốn biết mức dung lượng bộ nhớ mà tất cả quy trình sử dụng, vì các trang không được tính nhiều lần. PSS mất nhiều thời gian để tính toán vì hệ thống cần xác định những trang được chia sẻ và số lượng quy trình. RSS không phân biệt giữa các trang được chia sẻ và không được chia sẻ (giúp tính toán nhanh hơn) và phù hợp hơn để theo dõi thay đổi trong quá trình phân bổ bộ nhớ.

Tài nguyên khác