Việc giải phóng một đối tượng trong mã của ứng dụng hoặc đóng một luồng tạm thời không phải lúc nào cũng trả lại RAM vật lý cho hệ điều hành. Trình phân bổ gốc tuỳ chỉnh, thời gian chạy được thu gom rác và bảng băm địa chỉ mở thường giữ lại các vùng bộ nhớ ảo được các trang vật lý hỗ trợ trong thời gian dài sau khi dữ liệu logic bên trong chúng biến mất. Tương tự, việc đọc hoặc ánh xạ bộ nhớ các tệp lưu trữ tài sản lớn sẽ điền vào bộ nhớ đệm trang nhân, bộ nhớ này vẫn được tính vào nhóm kiểm soát bộ nhớ (memcg) của ứng dụng.
Trang này giải thích cách sử dụng lệnh gọi hệ thống madvise và posix_fadvise để trả về các trang ẩn danh và trang được hỗ trợ tệp không dùng đến cho hệ điều hành trong khi vẫn giữ nguyên các chỉ số mô tả tệp và lượt đặt trước địa chỉ ảo, đồng thời giải thích cách xác minh tác động trong adb shell và Perfetto.
Tại sao việc giải phóng bộ nhớ logic vẫn giữ lại RAM vật lý
Nhân Linux theo dõi bộ nhớ vật lý ở mức độ chi tiết của các trang phần cứng,
là 4 KB hoặc 16 KB trên thiết bị Android. Khi một quy trình đặt trước một vùng không gian địa chỉ ảo bằng mmap, hạt nhân sẽ tạo một vùng bộ nhớ ảo (VMA) mà không phân bổ RAM thực. Hạt nhân chỉ phân bổ và tính phí một trang thực khi quy trình chạm vào trang đó lần đầu tiên. Đối với bộ nhớ ẩn danh, lần đọc đầu tiên sẽ ánh xạ một trang không dùng chung, vì vậy, chỉ có thao tác ghi mới phân bổ RAM.
Sau khi một trang bị lỗi trong RAM, nhân hệ điều hành sẽ không biết liệu các byte bên trong trang đó có còn đại diện cho các đối tượng đang hoạt động trong thời gian chạy của ứng dụng hay không. Có hai mẫu phổ biến khiến các trang thực vẫn nằm trong bộ nhớ sau khi ứng dụng của bạn dùng xong:
- Vùng và nhóm ẩn danh: Trình phân bổ gốc tuỳ chỉnh (chẳng hạn như trình phân bổ slab, pool hoặc frame-arena), thời gian chạy được quản lý có không gian vùng nhớ khối xếp không di chuyển và bảng băm lớn sẽ dự trữ dải địa chỉ ảo liền kề bằng
mmapbằng cách sử dụngMAP_PRIVATE | MAP_ANONYMOUS. Khi ứng dụng của bạn đặt lại một vùng, giải phóng một khối trở lại vùng tuỳ chỉnh hoặc xoá bảng băm bằng cách đặt siêu dữ liệu về 0, các trang cơ bản vẫn bị bẩn. Nếu không có gợi ý rõ ràng cho hạt nhân, thì các trang đó sẽ tiếp tục được tính vào kích thước tập hợp thường trú (RSS) và kích thước tập hợp theo tỷ lệ (PSS) ẩn danh của quy trình và mức phí cgroupmemory.currentcủa quy trình đó. Khi bộ nhớ bị quá tải, nhân hệ điều hành sẽ nén các trang này vào ZRAM, nơi chúng vẫn chiếm không gian hoán đổi được tính chomemory.swap.current. - Bộ nhớ đệm trang dựa trên tệp từ tính năng truyền trực tuyến thành phần: Khi ứng dụng của bạn đọc các gói thành phần lớn, tệp lưu trữ cấp, vùng chứa nội dung nghe nhìn hoặc mô hình học máy bằng
read,preadhoặcmmapdựa trên tệp, hạt nhân sẽ lưu mọi trang đã đọc vào bộ nhớ đệm trang và tính các trang đó vào bộ đếmfiletrong tệpmemory.statcủa cgroup trong quy trình của bạn. Nếu ứng dụng của bạn giải nén hoặc sao chép thành phần đó vào vùng đệm thời gian chạy ẩn danh hoặc tải thành phần đó lên GPU, thì việc giữ các trang tệp thô trong bộ nhớ đệm trang sẽ sao chép thành phần đó trong RAM.
Phát hành trang ẩn danh
Lệnh gọi hệ thống madvise đưa ra lời khuyên cho nhân hệ điều hành về cách quy trình của bạn dự kiến sử dụng dải địa chỉ ảo. Đối với các ánh xạ ẩn danh riêng tư (MAP_PRIVATE |
MAP_ANONYMOUS), việc truyền MADV_DONTNEED sẽ hướng dẫn nhân hệ điều hành ngay lập tức loại bỏ các trang vật lý và mọi mục ZRAM đã hoán đổi hỗ trợ dải ô được căn chỉnh theo trang đã chỉ định.
Sau khi madvise với MADV_DONTNEED trả về:
- Dải địa chỉ ảo vẫn được đặt trước và hợp lệ trong không gian địa chỉ của quy trình (
VmSizekhông thay đổi), vì vậy, con trỏ và giới hạn vùng vẫn giữ nguyên mà không cần gọimunmaphoặcmmap. - Các trang vật lý sẽ ngay lập tức được huỷ liên kết và giải phóng, giúp giảm
RssAnon,Pss, bộ đếmanontrongmemory.statvàmemory.swap.currentcho mọi trang trong ZRAM. - Mọi thao tác đọc hoặc ghi tiếp theo vào một địa chỉ trong phạm vi đó đều bị lỗi trong một trang mới được điền số 0 theo yêu cầu.
| Cờ tư vấn | Liên kết mục tiêu | Hành vi của nhân hệ điều hành trên Android |
|---|---|---|
MADV_DONTNEED |
Ẩn danh riêng tư (MAP_PRIVATE | MAP_ANONYMOUS) |
Ngay lập tức loại bỏ các trang vật lý và vùng hoán đổi ZRAM. Các lượt truy cập tiếp theo sẽ gây ra lỗi trong các trang được điền bằng số 0. Mức giảm tất định trong RssAnon và memory.current. |
MADV_DONTNEED |
Được hỗ trợ bằng tệp (MAP_PRIVATE hoặc MAP_SHARED) |
Huỷ liên kết các trang khỏi bảng trang quy trình (RssFile
bị loại bỏ). Các trang sao chép khi ghi riêng tư trong một liên kết MAP_PRIVATE sẽ bị loại bỏ, vì vậy, các nội dung sửa đổi sẽ bị mất và lần truy cập tiếp theo sẽ đọc lại tệp. Các trang MAP_SHARED bẩn sẽ được ghi lại vào bộ nhớ sau. Các trang sạch sẽ nằm trong bộ nhớ đệm trang cho đến khi bị loại bỏ. |
MADV_FREE |
Ẩn danh riêng tư (MAP_PRIVATE | MAP_ANONYMOUS) |
Đánh dấu các trang là có thể sử dụng lại từng phần. Hạt nhân giữ lại các trang trong RssAnon nếu không có áp lực bộ nhớ và chỉ thu hồi các trang đó khi quá trình thu hồi chung hoặc memcg chạy. |
MADV_WILLNEED |
Ẩn danh được sao lưu hoặc hoán đổi tệp | Bắt đầu đọc trước không đồng bộ cho các tệp ánh xạ được hỗ trợ hoặc đọc trước các trang ẩn danh đã hoán đổi từ ZRAM vào bộ nhớ đệm hoán đổi. |
Căn chỉnh các dải ô bên trong theo kích thước trang thời gian chạy
madvise hoạt động trên toàn bộ trang. Hàm này yêu cầu địa chỉ bắt đầu được căn chỉnh theo trang và làm tròn độ dài đến ranh giới trang tiếp theo. Nếu bạn giải phóng một span bên trong một
vùng nhớ dùng chung, nơi các byte lân cận vẫn giữ các đối tượng đang hoạt động, thì việc làm tròn phạm vi
ra ngoài có thể làm mất các lượt phân bổ đang hoạt động có chung các trang ranh giới.
Luôn truy vấn kích thước trang thời gian chạy bằng sysconf(_SC_PAGESIZE) để hỗ trợ cả thiết bị trang 4 KB và 16 KB, đồng thời làm tròn địa chỉ bắt đầu bên trong lên ranh giới trang tiếp theo và địa chỉ kết thúc bên trong xuống ranh giới trang trước. Để biết thêm hướng dẫn về trình phân bổ không phụ thuộc vào kích thước trang, hãy xem phần Tối ưu hoá trình phân bổ bộ nhớ tuỳ chỉnh.
#include <sys/mman.h>
#include <unistd.h>
#include <cstdint>
// Reclaims physical pages strictly inside [region_start, region_start + length)
// without disturbing live objects on partially occupied boundary pages.
void ReleaseUnusedArenaSpan(void* region_start, size_t length) {
const uintptr_t page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
const uintptr_t page_mask = ~(page_size - 1);
const uintptr_t raw_start = reinterpret_cast<uintptr_t>(region_start);
const uintptr_t raw_end = raw_start + length;
// Round start UP and end DOWN so only fully unused pages are discarded.
const uintptr_t aligned_start = (raw_start + page_size - 1) & page_mask;
const uintptr_t aligned_end = raw_end & page_mask;
if (aligned_end > aligned_start) {
madvise(reinterpret_cast<void*>(aligned_start),
aligned_end - aligned_start,
MADV_DONTNEED);
}
}
Khi tạo mối liên kết ẩn danh tuỳ chỉnh bằng mmap, hãy gắn nhãn VMA bằng prctl bằng cách sử dụng PR_SET_VMA_ANON_NAME để có thể theo dõi trạng thái lưu trú của VMA theo tên trong showmap và /proc/<pid>/smaps. dumpsys
meminfo tính các vùng ẩn danh có tên tuỳ chỉnh trong Unknown.
Một số nhân cũ giữ con trỏ đến tên thay vì sao chép tên, vì vậy, hãy truyền một chuỗi vẫn hợp lệ trong suốt thời gian ánh xạ, chẳng hạn như một chuỗi cố định:
#include <sys/mman.h>
#include <sys/prctl.h>
#ifndef PR_SET_VMA
#define PR_SET_VMA 0x53564d41
#define PR_SET_VMA_ANON_NAME 0
#endif
void* ReserveNamedArena(size_t capacity_bytes, const char* arena_name) {
void* ptr = mmap(nullptr, capacity_bytes, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr != MAP_FAILED) {
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME,
reinterpret_cast<unsigned long>(ptr), capacity_bytes,
reinterpret_cast<unsigned long>(arena_name));
}
return ptr;
}
Xoá các trang bộ nhớ đệm của tệp
Khi ứng dụng của bạn đọc dữ liệu từ một chỉ số mô tả tệp, hạt nhân sẽ lưu các trang của tệp vào bộ nhớ đệm trang để chuẩn bị cho các lần đọc trong tương lai. Mặc dù việc này giúp tăng tốc độ truy cập lặp lại vào các tệp cấu hình nhỏ hoặc thư viện dùng chung, nhưng lại gây bất lợi khi truyền trực tuyến các tệp lưu trữ thành phần lớn, gói kết cấu hoặc các đoạn âm thanh và video được phân tích cú pháp một lần và lưu trữ ở dạng đã giải mã ở nơi khác.
Lệnh gọi hệ thống posix_fadvise cho phép ứng dụng của bạn thông báo cho nhân hệ điều hành về mẫu truy cập tệp dự kiến trên một chỉ số mô tả tệp đang mở:
POSIX_FADV_DONTNEED: Hướng dẫn hạt nhân rằng phạm vi byte đã chỉ định sẽ không được truy cập trong tương lai gần. Hạt nhân ngay lập tức loại bỏ các trang được lưu vào bộ nhớ đệm sạch, chưa được liên kết trong dải ô được căn chỉnh theo trang đó khỏi bộ nhớ đệm trang, giảm bộ đếmfiletrongmemory.statvà mức phímemory.currentcủa cgroup.POSIX_FADV_SEQUENTIAL: Tăng gấp đôi cửa sổ đọc trước của hạt nhân cho chỉ số mô tả tệp, cải thiện thông lượng khi truyền trực tuyến một tệp lưu trữ lớn từ đầu đến cuối. Hãy kết hợp thao tác này vớiPOSIX_FADV_DONTNEEDtrên các đoạn cuối khi bạn sử dụng chúng để mức sử dụng bộ nhớ đệm của trang luôn nằm trong một cửa sổ trượt nhỏ.POSIX_FADV_RANDOM: Tắt tính năng đọc trước suy đoán của nhân hệ điều hành trên chỉ số mô tả tệp. Sử dụng chế độ này khi thực hiện các lượt tra cứu thưa thớt, không liền kề theo độ lệch byte vào một tệp lưu trữ hoặc tệp cơ sở dữ liệu lớn để mỗi lượt đọc nhỏ không kéo các trang không dùng đến liền kề vào bộ nhớ đệm trang. Đối với các tệp được liên kết với bộ nhớ, hãy gọimadvisebằngMADV_RANDOMtrên mối liên kết.
Ví dụ sau đây truyền trực tuyến một bản lưu trữ thành phần lớn theo từng phần, cho phép đọc trước tuần tự và loại bỏ từng dải ô được căn chỉnh theo trang đã xử lý khỏi bộ nhớ đệm trang:
#include <fcntl.h>
#include <unistd.h>
#include <cstdint>
#include <vector>
void StreamAssetArchive(int fd, size_t total_size) {
const size_t page_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
constexpr size_t kChunkSize = 4 * 1024 * 1024; // 4 MB streaming window
std::vector<uint8_t> buffer(kChunkSize);
// Hint sequential access so the kernel prefetches upcoming chunks.
posix_fadvise(fd, 0, total_size, POSIX_FADV_SEQUENTIAL);
off_t offset = 0;
while (offset < static_cast<off_t>(total_size)) {
ssize_t bytes_read = pread(fd, buffer.data(), buffer.size(), offset);
if (bytes_read <= 0) break;
// Decode or upload buffer[0 .. bytes_read) into runtime structures here.
// Evict full pages that have been completely consumed.
const size_t aligned_len =
(static_cast<size_t>(bytes_read) / page_size) * page_size;
if (aligned_len > 0) {
posix_fadvise(fd, offset, static_cast<off_t>(aligned_len),
POSIX_FADV_DONTNEED);
}
offset += bytes_read;
}
}
Thu hồi các vùng mmap được tệp hỗ trợ
Nếu ứng dụng của bạn ánh xạ bộ nhớ cho một tệp thành phần lớn bằng mmap thay vì đọc tệp đó bằng pread, thì việc chỉ gọi posix_fadvise bằng POSIX_FADV_DONTNEED sẽ không loại bỏ các trang vẫn được ánh xạ vào bảng trang của quy trình. Cách bỏ một
khoảng đã sử dụng của tệp ánh xạ chỉ có thể đọc mà không huỷ ánh xạ toàn bộ VMA:
- Gọi
madvise(addr, length, MADV_DONTNEED)trên dải địa chỉ ảo được liên kết để huỷ liên kết các trang đó khỏi bảng trang của quy trình (RssFilegiảm). - Gọi
posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED)trên độ lệch tệp tương ứng để hạt nhân cũng loại bỏ các trang sạch chưa được ánh xạ khỏi bộ nhớ đệm trang (filetrongmemory.statgiảm).
Tuân thủ nguyên tắc về lời khuyên cho trang
Hãy áp dụng các phương pháp này khi tích hợp madvise và posix_fadvise vào
bộ cấp phát, trình tải thành phần và trình xử lý vòng đời:
- Gom các lệnh gọi thành các vùng thô: Mỗi lệnh gọi
madvisesẽ lấy khoá bản đồ bộ nhớ của quy trình (mmap_lock), duyệt qua các bảng trang và vô hiệu hoá các mục nhập bộ đệm dịch tra cứu (TLB) phù hợp trên các lõi CPU khác (bằng hướng dẫn bảo trì TLB truyền tin trên Arm và các lệnh ngắt giữa các bộ xử lý trên x86). Đừng gọimadvisecho mọi lần giải phóng bộ nhớ nhỏ. Kết hợp các khoảng trống và thông báo cho nhân hệ điều hành theo từng đoạn từ 64 KB đến 1 MB trở lên, hoặc quét các khoảng trống trong ranh giới khung hình và chuyển đổi nền (onStopvàTRIM_MEMORY_UI_HIDDEN). - Thu hồi dung lượng trống trong bảng băm và vùng nhớ: Khi một bảng băm, vùng nhớ đối tượng hoặc vùng đệm thử nghiệm lớn bị xoá sau khi chuyển đổi cảnh hoặc đồng bộ hoá dữ liệu, hãy thu nhỏ mức phân bổ dự phòng hoặc phát hành
MADV_DONTNEEDtrên mảng dự phòng đã xoá. Việc giữ một bảng địa chỉ mở 32 MB được phân bổ bằng các dấu mốc sẽ khiến tất cả 32 MB trang vật lý được tính vào quy trình của bạn. - Xoá dữ liệu tệp bẩn trước khi loại bỏ dữ liệu đó:
POSIX_FADV_DONTNEEDchỉ loại bỏ các trang bộ nhớ đệm trang sạch. Nếu ứng dụng của bạn ghi một tệp tạm thời hoặc tệp bộ nhớ đệm lớn mà không đọc lại ngay, hãy gọifdatasynctrướcposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)để các trang bẩn được ghi vào bộ nhớ rồi bị loại bỏ khỏi RAM. - Không loại bỏ mã hoặc thành phần dùng chung thường xuyên: Tránh gọi
POSIX_FADV_DONTNEEDtrên DEX, ODEX, VDEX, thư viện dùng chung (.so) hoặc thành phần giao diện người dùng được truy cập nhiều lần. Việc loại bỏ các trang mã hoặc trang Thành phần đang hoạt động sẽ buộc phải đồng bộ hoá lại bộ nhớ, khiến các luồng bị đình trệ và tăng áp lực I/O, như được báo cáo bởi thông tin về áp lực đình trệ (PSI).
Xác minh việc thu hồi trang trong shell
Xác minh rằng madvise và posix_fadvise lấy lại RAM thực bằng showmap và các tệp lưu giữ hồ sơ cgroup v2 của ứng dụng.
Kiểm tra kích thước ảo so với kích thước thực bằng showmap
Khi bạn đặt tên cho một vùng ẩn danh bằng PR_SET_VMA_ANON_NAME, showmap sẽ hiển thị cả lượt đặt trước ảo (virtual size) và các trang thực tế thường trú (RSS và PSS). Kết quả mẫu sau đây là từ ứng dụng mẫu MemoryLab trong AOSP, đặt tên cho vùng của ứng dụng là memorylab-arena. Thay thế com.example.app và memorylab-arena bằng tên gói và tên đấu trường của bạn. Trước khi gọi madvise bằng MADV_DONTNEED trên một vùng 64 MB đã chạm, hãy chạy:
adb shell "showmap \$(pidof com.example.app) | grep -E '^ +virtual|^ +size|memorylab-arena' | head -3"
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
65536 65536 65536 0 0 0 65536 0 0 0 0 0 0 0 0 1 [anon:memorylab-arena]
Sau khi gọi madvise(arena_ptr, 64 * 1024 * 1024, MADV_DONTNEED), hãy chạy lại showmap:
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
65536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 [anon:memorylab-arena]
virtual size vẫn ở mức 65536 KB (64 MB), xác nhận rằng dải địa chỉ ảo vẫn được đặt trước, trong khi RSS, PSS và private dirty giảm xuống 0 KB.
Kiểm tra memory.current và memory.stat của cgroup
Để xác minh cả việc thu hồi trang ẩn danh và loại bỏ bộ nhớ đệm trang chưa được liên kết (không xuất hiện trong bảng trang quy trình nếu đọc bằng pread), hãy kiểm tra thư mục cgroup v2 của quy trình. Trên Android, mỗi quy trình ứng dụng đều có cgroup riêng (chẳng hạn như /sys/fs/cgroup/apps/uid_<UID>/pid_<PID>/, tuỳ thuộc vào thiết bị).
Lệnh sau đây tìm thư mục từ dòng 0:: của /proc/<PID>/cgroup và in bộ đếm:
adb shell '
PID=$(pidof com.example.app)
CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
echo "memory.current: $(cat $CG/memory.current)"
echo "memory.swap.current: $(cat $CG/memory.swap.current)"
grep -E "^(anon|file) " $CG/memory.stat
'
Trước khi giải phóng một vùng nhớ ẩn danh 64 MB và loại bỏ một tệp tài sản được ánh xạ bộ nhớ 128 MB khỏi bộ nhớ đệm trang:
memory.current: 242962432
memory.swap.current: 16633856
anon 100671488
file 140009472
Ngay sau khi gọi madvise bằng MADV_DONTNEED trên vùng nhớ và trên
ánh xạ tệp tài sản, sau đó gọi posix_fadvise bằng POSIX_FADV_DONTNEED trên
chỉ số mô tả tệp tài sản:
memory.current: 41766912
memory.swap.current: 16502784
anon 33722368
file 5914624
anon giảm 64 MB và file giảm 128 MB, giảm memory.current khoảng 192 MB mà không cần đóng chỉ số mô tả tệp hoặc huỷ liên kết không gian địa chỉ ảo của vùng nhớ. memory.swap.current hầu như không thay đổi vì gần như không có trang vùng nào được hoán đổi sang ZRAM. Giá trị của bạn phụ thuộc vào thiết bị và lượng tệp mà nhân hệ điều hành đã tự loại bỏ. Để biết thêm thông tin về cách nhân tính phí và thu hồi bộ nhớ cho mỗi cgroup, hãy xem phần Thu hồi bộ nhớ: loại bỏ và hoán đổi.
Phân tích việc thu hồi trang trong Perfetto
Perfetto cho phép bạn liên kết các lệnh gọi madvise và posix_fadvise với bộ đếm bảng trang nhân và bộ đếm bộ nhớ đệm trang trên một dòng thời gian hợp nhất.
Ghi lại dấu vết
Ghi lại dấu vết bằng linux.process_stats cho bộ đếm quy trình định kỳ và linux.ftrace cho các sự kiện RSS và bộ nhớ đệm trang theo thời gian thực, sau đó kéo dấu vết đó vào máy tính của bạn:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/page_advice.perfetto-trace <<EOF
buffers: {
size_kb: 65536
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 100
}
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "kmem/rss_stat"
ftrace_events: "filemap/mm_filemap_add_to_page_cache"
ftrace_events: "filemap/mm_filemap_delete_from_page_cache"
}
}
}
duration_ms: 20000
EOF
adb pull /data/misc/perfetto-traces/page_advice.perfetto-trace
Kiểm tra các kênh trong giao diện người dùng Perfetto
Mở dấu vết trong giao diện người dùng Perfetto, mở rộng nhóm quy trình của ứng dụng và ghim các bản ghi bộ đếm sau đây để chúng luôn ở đầu dòng thời gian:
mem.virt: Giữ nguyên trong các lệnh gọiMADV_DONTNEED, xác nhận rằng việc đặt trước địa chỉ ảo (mmap) không thay đổi.mem.rss.anon: Giảm mạnh tại dấu thời gian chính xác mà ứng dụng của bạn gọimadvisebằngMADV_DONTNEEDtrên các vùng nhớ khối xếp hoặc vùng nhớ khối xếp ẩn danh bẩn.mem.rss.file: Giảm xuống khi ứng dụng của bạn gọimadvisebằngMADV_DONTNEEDtrên một vùngmmapđược sao lưu tệp, kèm theo một loạt sự kiện ftracemm_filemap_delete_from_page_cachekhiposix_fadvisebằngPOSIX_FADV_DONTNEEDloại bỏ các trang sạch khỏi bộ nhớ đệm trang.mem.swap: Giảm khiMADV_DONTNEEDloại bỏ các trang ẩn danh đã được nén vào ZRAM trước đó.
Hình 2 cho thấy các kênh này cho ứng dụng mẫu MemoryLab. Để khám phá cùng một chế độ xem, hãy mở dấu vết mẫu trong giao diện người dùng Perfetto.
Truy vấn bộ nhớ cao nhất và bộ nhớ cuối cùng bằng PerfettoSQL
Chạy truy vấn PerfettoSQL sau đây trong giao diện người dùng Perfetto (thẻ Query (SQL) (Truy vấn (SQL))) để so sánh giá trị cao nhất và giá trị cuối cùng của mem.virt, mem.rss.anon, mem.rss.file và mem.swap cho quy trình mục tiêu:
WITH counters AS (
SELECT t.name AS counter_name, c.ts, c.value
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name = 'com.example.app'
AND t.name IN ('mem.virt', 'mem.rss.anon', 'mem.rss.file', 'mem.swap')
)
SELECT
counter_name,
CAST(MAX(value) / 1048576 AS INT) AS peak_mb,
CAST((
SELECT value FROM counters AS last
WHERE last.counter_name = counters.counter_name
ORDER BY ts DESC LIMIT 1
) / 1048576 AS INT) AS final_mb
FROM counters
GROUP BY counter_name
ORDER BY counter_name;
Kết quả mẫu cho dấu vết trong Hình 2:
counter_name peak_mb final_mb
mem.rss.anon 111 48
mem.rss.file 212 84
mem.swap 36 36
mem.virt 13952 13952
mem.rss.anon giảm 63 MB và mem.rss.file giảm 128 MB so với mức cao nhất, trong khi mem.virt kết thúc ở mức cao nhất vì các lượt đặt trước ảo không thay đổi.
Giải quyết các trang bị loại bỏ khỏi tệp
Sự kiện mm_filemap_add_to_page_cache và mm_filemap_delete_from_page_cache xác định tệp theo số inode (i_ino) thay vì theo đường dẫn. Để xác nhận rằng các trang bị loại bỏ thuộc về tệp mà bạn đã thông báo, hãy đếm số lần loại bỏ trên mỗi inode:
SELECT
EXTRACT_ARG(arg_set_id, 'i_ino') AS inode,
COUNT(*) AS evicted_pages
FROM ftrace_event
WHERE name = 'mm_filemap_delete_from_page_cache'
GROUP BY inode
ORDER BY evicted_pages DESC
LIMIT 5;
Kết quả mẫu cho dấu vết trong Hình 2:
inode evicted_pages
9761 35781
76792192 767
126 668
0 612
11587712 514
Trong khi thiết bị ghi lại dấu vết vẫn được kết nối, hãy phân giải từng inode thành một đường dẫn. Số inode chỉ là duy nhất trong một hệ thống tệp, vì vậy, hãy giới hạn phạm vi tìm kiếm trong hệ thống tệp chứa dữ liệu của ứng dụng:
INODE=<inode from the query>
adb shell "find /data -xdev -inum $INODE 2>/dev/null"
Kết quả mẫu cho inode 9761:
/data/user/0/com.android.memorylab/files/asset_pack.bin
/data/data/com.android.memorylab/files/asset_pack.bin
Cả hai đường dẫn đều tham chiếu đến cùng một tệp, vì /data/user/0 và /data/data là hai tên cho cùng một thư mục dữ liệu ứng dụng. Số lần loại bỏ cho tệp tài sản (35.781 trang) lớn hơn chính tệp đó (32.768 trang ở mức 4 KB), vì kswapd đã loại bỏ một phần của tệp dưới áp lực bộ nhớ trước khi ứng dụng đọc lại và gọi posix_fadvise.
Để biết tập lệnh giải quyết nhiều inode cùng một lúc, hãy xem phần Giải quyết lỗi cho inode, sau đó cho tệp.
← Tương tác giữa kswapd và lmkd | ↑ Lên | Tinh gọn quy trình trong nền →