Tổng quan về hoạt động quản lý bộ nhớ

Máy ảo Android Runtime (ART) và Dalvik sử dụng các tuỳ chọn phân trangánh xạ bộ nhớ (mmapping) để quản lý bộ nhớ. Điều này có nghĩa là mọi bộ nhớ mà một ứng dụng sửa đổi (cho dù bằng cách phân bổ các đối tượng mới hay tiếp xúc với các trang được ánh xạ) sẽ vẫn nằm trong RAM và không thể chuyển sang bộ nhớ phụ. Cách duy nhất để giải phóng bộ nhớ khỏi một ứng dụng là giải phóng các tệp tham chiếu đối tượng mà ứng dụng lưu giữ, cung cấp bộ nhớ này cho trình thu gom rác. Có một trường hợp ngoại lệ là bất kỳ tệp nào được ánh xạ bộ nhớ mà không cần chỉnh sửa, chẳng hạn như mã, đều có thể bị chuyển ra khỏi RAM nếu hệ thống muốn dùng bộ nhớ đó ở nơi khác.

Trang này giải thích cách Android quản lý các quy trình ứng dụng và hoạt động phân bổ bộ nhớ. Để biết thêm thông tin về cách quản lý bộ nhớ sao cho hiệu quả hơn trong ứng dụng của bạn, vui lòng xem bài viết Quản lý bộ nhớ của ứng dụng.

Thu gom rác

Một môi trường bộ nhớ được quản lý, như ART hoặc máy ảo Dalvik, sẽ tiếp tục theo dõi từng lượt phân bổ bộ nhớ. Sau khi xác định một phần bộ nhớ không còn được chương trình sử dụng, chương trình sẽ giải phóng bộ nhớ trở lại vùng nhớ khối xếp mà không cần sự can thiệp của lập trình viên. Cơ chế thu hồi bộ nhớ không sử dụng trong môi trường bộ nhớ được quản lý được gọi là thu gom rác. Việc thu gom rác có hai mục tiêu: tìm các đối tượng dữ liệu trong một chương trình không thể truy cập trong tương lai; và thu hồi tài nguyên mà các đối tượng đó sử dụng.

Vùng nhớ khối xếp của Android là một vùng nhớ mang tính thế hệ, nghĩa là có nhiều nhóm phân bổ mà vùng nhớ theo dõi, dựa trên vòng đời và kích thước dự kiến của một đối tượng được phân bổ. Ví dụ: các đối tượng được phân bổ gần đây thuộc về Thế hệ trẻ. (vùng nhớ chứa các đối tượng mới được khởi tạo). Khi một đối tượng hoạt động đủ lâu, đối tượng này có thể được quảng bá cho một thế hệ cũ, theo sau là một thế hệ vĩnh viễn.

Mỗi thế hệ của vùng nhớ khối xếp có giới hạn trên riêng về dung lượng bộ nhớ mà các đối tượng ở đó có thể chiếm. Bất cứ khi nào một thế hệ bắt đầu đầy, hệ thống sẽ thực thi một sự kiện thu gom rác để giải phóng bộ nhớ. Thời gian thu gom rác tuỳ thuộc vào thế hệ đối tượng mà nó đang thu thập và số lượng đối tượng đang hoạt động trong mỗi thế hệ.

Mặc dù việc thu gom rác có thể khá nhanh, nhưng vẫn có thể ảnh hưởng đến hiệu suất của ứng dụng. Bạn thường không kiểm soát được thời điểm xảy ra sự kiện thu gom rác bên trong mã của mình. Hệ thống có một bộ tiêu chí đang chạy để xác định thời điểm tiến hành thu gom rác. Khi các tiêu chí được đáp ứng, hệ thống sẽ ngừng thực thi quy trình này và bắt đầu thu gom rác. Nếu việc thu gom rác xảy ra ở giữa vòng lặp xử lý chuyên sâu như ảnh động hoặc trong khi phát nhạc, thì thời gian xử lý có thể kéo dài ra. Việc tăng này có thể đẩy quá trình thực thi mã trong ứng dụng của bạn vượt quá ngưỡng 16 mili giây được đề xuất để khung hình có thể hiển thị một cách hiệu quả và mượt mà.

Ngoài ra, quy trình mã của bạn có thể thực hiện những loại tác vụ buộc các sự kiện thu gom rác xảy ra thường xuyên hơn hoặc kéo dài lâu hơn bình thường. Chẳng hạn như nếu bạn phân bổ nhiều đối tượng ở phần trong cùng của một vòng lặp trong mỗi khung hình của ảnh động kết hợp alpha, bạn có thể làm ô nhiễm vùng nhớ khối xếp của mình vì có quá nhiều đối tượng. Trong trường hợp đó, trình thu gom rác phải thực hiện nhiều sự kiện thu gom rác và có thể làm giảm hiệu suất của ứng dụng.

Để biết thêm thông tin chung về việc thu gom rác, vui lòng xem bài viết Thu gom rác.

Chia sẻ bộ nhớ

Để đáp ứng được mọi yêu cầu về RAM, Android sẽ cố gắng chia sẻ các trang RAM trên các quy trình. Bạn có thể làm như vậy theo các cách sau:

  • Mỗi quy trình của ứng dụng đều được tách ra từ một quy trình hiện có được gọi là Zygote. Quá trình Zygote bắt đầu khi hệ thống khởi động và tải mã khung cũng như các tài nguyên thông thường (chẳng hạn như các chủ đề hoạt động). Để bắt đầu một quy trình mới đối với ứng dụng, hệ thống sẽ phân tách phát triển nhánh quy trình Zygote, sau đó tải và chạy mã của ứng dụng trong quy trình mới. Phương pháp này cho phép hầu hết các trang RAM được phân bổ cho mã khung và tài nguyên dùng chung trên tất cả các quy trình của ứng dụng.
  • Hầu hết dữ liệu tĩnh được liên kết vào một quy trình. Kỹ thuật này cho phép chia sẻ dữ liệu giữa các quy trình, đồng thời cho phép phân phát dữ liệu khi cần thiết. Ví dụ về dữ liệu tĩnh bao gồm: mã Dalvik (bằng cách đặt mã đó vào tệp .odex được liên kết trước để ánh xạ bộ nhớ trực tiếp), tài nguyên ứng dụng (bằng cách thiết kế bảng tài nguyên thành một cấu trúc có thể được ánh xạ bộ nhớ và căn chỉnh các mục nhập zip của APK), cũng như các phần tử dự án truyền thống như mã gốc trong tệp .so.
  • Ở nhiều nơi, Android chia sẻ cùng một RAM động trên các quy trình bằng cách sử dụng vùng bộ nhớ dùng chung được phân bổ rõ ràng (với ashmem hoặc gralloc). Ví dụ như các nền tảng cửa sổ sử dụng bộ nhớ dùng chung giữa trình kết hợp dữ liệu màn hình và ứng dụng, còn vùng đệm con trỏ sử dụng bộ nhớ dùng chung giữa trình cung cấp nội dung và ứng dụng.

Do việc sử dụng rộng rãi bộ nhớ dùng chung, nên việc xác định dung lượng bộ nhớ mà ứng dụng của bạn đang sử dụng cần phải được xử lý cẩn thận. Các kỹ thuật để xác định đúng cách việc sử dụng bộ nhớ của ứng dụng sẽ được thảo luận trong bài viết Điều tra mức sử dụng RAM.

Phân bổ và thu hồi bộ nhớ ứng dụng

Vùng nhớ khối xếp Dalvik bị hạn chế trong một phạm vi bộ nhớ ảo duy nhất cho mỗi quy trình ứng dụng. Điều này xác định kích thước vùng nhớ khối xếp hợp lý, kích thước này có thể tăng lên khi cần, nhưng chỉ đến một giới hạn mà hệ thống xác định cho từng ứng dụng.

Kích thước hợp lý của vùng nhớ khối xếp không giống với dung lượng bộ nhớ thực mà vùng nhớ khối xếp sử dụng. Khi kiểm tra vùng nhớ khối xếp của ứng dụng, Android sẽ tính toán một giá trị được gọi là Kích thước cài đặt theo tỷ lệ (PSS). Giá trị này tính cho cả dirty page (trang có dữ liệu đã sửa đổi nhưng chưa được chuyển sang ổ đĩa) và clean page (trang có dữ liệu đã sửa đổi và được chuyển sang ổ đĩa) đã chia sẻ với các quy trình khác – nhưng chỉ trong một số lượng tương ứng với số lượng ứng dụng chia sẻ RAM đó. Tổng số (PSS) này là giá trị mà hệ thống coi là mức sử dụng bộ nhớ thực của bạn. Để biết thêm thông tin về PSS, vui lòng xem hướng dẫn Điều tra mức sử dụng RAM.

Bộ nhớ khối xếp Dalvik không thu gọn kích thước hợp lý của vùng nhớ khối xếp, nghĩa là Android không phân đoạn vùng nhớ khối xếp để đóng dung lượng. Android chỉ có thể thu nhỏ kích thước vùng nhớ khối xếp hợp lý khi có dung lượng chưa được sử dụng ở cuối vùng nhớ khối xếp. Tuy nhiên, hệ thống vẫn có thể giảm bộ nhớ thực mà vùng nhớ khối xếp sử dụng. Sau khi thu gom rác, Dalvik sẽ di chuyển qua vùng nhớ khối xếp và tìm các trang không sử dụng, sau đó sử dụng madvise để trả các trang đó về kernel. Vì vậy, việc phân bổ theo cặp và giải phóng các khúc dữ liệu lớn sẽ dẫn đến tình trạng thu hồi tất cả (hoặc gần như tất cả) bộ nhớ thực đã dùng. Tuy nhiên, việc thu hồi bộ nhớ từ các lượt phân bổ kích thước nhỏ có thể kém hiệu quả hơn nhiều, vì trang dùng cho lượt phân bổ kích thước nhỏ vẫn có thể được chia sẻ với một nội dung khác chưa được giải phóng.

Hạn chế bộ nhớ của ứng dụng

Để duy trì một môi trường hoạt động đa nhiệm, Android đã đặt giới hạn cố định về kích thước vùng nhớ khối xếp cho từng ứng dụng. Giới hạn kích thước vùng nhớ khối xếp chính xác sẽ khác nhau giữa các thiết bị dựa trên dung lượng RAM mà thiết bị có sẵn về tổng thể. Nếu ứng dụng của bạn đã đạt đến hạn mức vùng nhớ khối xếp và đang cố gắng phân bổ thêm bộ nhớ, ứng dụng có thể sẽ nhận một OutOfMemoryError.

Trong một số trường hợp, bạn có thể muốn truy vấn hệ thống để xác định chính xác dung lượng vùng nhớ khối xếp sẵn có trên thiết bị hiện tại. Ví dụ như để xác định lượng dữ liệu an toàn để lưu trong một bộ nhớ đệm. Bạn có thể truy vấn hệ thống cho minh hoạ này bằng cách gọi getMemoryClass(). Phương thức này trả về một số nguyên cho biết số megabyte có sẵn cho vùng nhớ khối xếp của ứng dụng.

Chuyển đổi giữa các ứng dụng

Khi người dùng chuyển đổi giữa các ứng dụng, Android sẽ giữ cho các ứng dụng không hiển thị trên nền trước (nghĩa là không hiển thị cho người dùng, hay chạy một dịch vụ trên nền trước như phát nhạc) trong bộ nhớ đệm. Chẳng hạn như khi người dùng khởi chạy một ứng dụng lần đầu, hệ thống sẽ tạo quy trình cho ứng dụng đó; nhưng khi người dùng rời khỏi ứng dụng, quy trình đó sẽ không thoát. Hệ thống sẽ giữ cho quá trình này được lưu vào bộ nhớ đệm. Nếu sau đó người dùng quay lại ứng dụng, hệ thống sẽ sử dụng lại quy trình này, nhờ đó giúp ứng dụng chuyển đổi nhanh hơn.

Nếu ứng dụng của bạn có quy trình được lưu trong bộ nhớ đệm và vẫn giữ lại các tài nguyên mà ứng dụng hiện không cần, thì ứng dụng sẽ gây ảnh hưởng đến hiệu suất chung của hệ thống, ngay cả khi người dùng không sử dụng ứng dụng đó. Khi sắp hết các tài nguyên chẳng hạn như bộ nhớ, hệ thống sẽ loại bỏ các quy trình trong bộ nhớ đệm. Hệ thống cũng tính đến các quy trình chiếm nhiều bộ nhớ nhất và có thể kết thúc các quy trình đó để giải phóng dung lượng RAM.

Lưu ý: Ứng dụng càng tiêu tốn ít bộ nhớ khi đang ở bộ nhớ đệm, thì càng nhiều khả năng không bị hỏng và có thể nhanh chóng thực hiện chức năng tiếp tục. Tuy nhiên, tuỳ thuộc vào các yêu cầu tức thì của hệ thống, các quy trình đã lưu vào bộ nhớ đệm có thể bị chấm dứt bất kỳ lúc nào, bất kể mức sử dụng tài nguyên của các quy trình đó.

Để biết thêm thông tin về cách các quy trình được lưu vào bộ nhớ đệm khi không chạy ở nền trước, cũng như cách Android quyết định những quy trình có thể bị huỷ, vui lòng xem hướng dẫn Quy trình và luồng.

Kiểm thử tình trạng áp lực bộ nhớ

Mặc dù tình trạng áp lực bộ nhớ ít xảy ra trên các thiết bị cao cấp, nhưng vẫn có thể gây ra vấn đề cho người dùng trên thiết bị có dung lượng RAM thấp, chẳng hạn như thiết bị chạy Android (phiên bản Go). Bạn phải thử và tái hiện lại môi trường gặp áp lực về bộ nhớ này để có thể viết các chương trình kiểm thử đo lường nhằm xác minh hành vi của ứng dụng và cải thiện trải nghiệm cho người dùng trên các thiết bị có bộ nhớ thấp.

Kiểm thử ứng dụng nghiêm ngặt

Kiểm thử ứng dụng nghiêm ngặt (stressapptest) là một quy trình kiểm thử giao diện bộ nhớ giúp tạo các tình huống thực tế, có mức tải cao để kiểm thử nhiều giới hạn về bộ nhớ và phần cứng cho ứng dụng. Với khả năng xác định giới hạn thời gian và bộ nhớ, loại kiểm thử này giúp bạn ghi khả năng đo lường để xác minh các tình huống trong thực tế sử dụng bộ nhớ ở mức cao. Ví dụ: sử dụng bộ lệnh sau để đẩy thư viện tĩnh vào hệ thống tệp dữ liệu của bạn, tạo tệp có thể thực thi và chạy chương trình kiểm thử nghiêm ngặt trong 20 giây cho dung lượng 990 MB:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

Hãy xem tài liệu về stressapptest để biết thêm thông tin về cách cài đặt công cụ, các đối số thường dùng và thông tin xử lý lỗi.

Thông tin quan sát về stressapptest

Bạn có thể sử dụng các công cụ như stressapptest để yêu cầu phân bổ bộ nhớ lớn hơn mức có sẵn. Loại yêu cầu này có thể đưa ra nhiều cảnh báo mà bạn cần lưu ý trên khía cạnh phát triển. Sau đây là 3 cảnh báo chính có thể xuất hiện do dung lượng bộ nhớ thấp:
  • SIGABRT: Đây là sự cố gốc nghiêm trọng trong quy trình của bạn do yêu cầu lượt phân bổ có kích thước lớn hơn bộ nhớ còn trống, trong khi hệ thống đang bị áp lực về bộ nhớ.
  • SIGQUIT: Tạo ra một vùng nhớ khối xếp bộ nhớ cốt lõi và chấm dứt quy trình khi phát hiện thấy hoạt động kiểm thử đo lường.
  • TRIM_MEMORY_EVENTS: Các lệnh gọi lại này có trên Android 4.1 (API cấp 16) trở lên và cung cấp cảnh báo chi tiết về bộ nhớ cho quy trình của bạn.