Khi bạn cần điều tra mức sử dụng bộ nhớ, hãy bắt đầu bằng các công cụ đánh giá cấp cao để xác định danh mục bộ nhớ nào quan trọng nhất.
dumpsys meminfo
Cách phổ biến nhất để xem thông tin tổng quan về mức sử dụng bộ nhớ của ứng dụng là
dumpsys meminfo. Để xem thông tin chi tiết bao gồm tất cả các mục ánh xạ bộ nhớ, bạn nên thêm cờ -a:
adb shell dumpsys meminfo -a <package_name_or_pid>
Dưới đây là kết quả mẫu của một ứng dụng chịu áp lực bộ nhớ (lưu ý SwapPss cao):
** MEMINFO in pid 4562 [com.android.memorylab] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 394 268 64 17440 4264 29416 18128 8040
Dalvik Heap 165 8 0 672 8196 40949 32757 8192
Dalvik Other 47 28 0 604 1132
Stack 112 108 4 312 120
...
TOTAL 33197 580 788 21920 167776 70365 50885 16232
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 12 32608
Native Heap: 268 4264
Code: 724 125672
Stack: 108 120
Graphics: 0 0
Private Other: 256
System: 31829
Unknown: 5112
TOTAL PSS: 33197 TOTAL RSS: 167776 TOTAL SWAP PSS: 21920
Objects
Views: 15 ViewRootImpl: 1
AppContexts: 5 Activities: 1
...
Diễn giải dữ liệu
- Pss Total: Kích thước cài đặt theo tỷ lệ. Đây là tổng của
Private DirtyPrivate Clean+ phần bộ nhớ công bằng của quy trình được chia sẻ với các quy trình khác (như hình ảnh khởi động Zygote). Đây là chỉ số tốt nhất cho biết "ứng dụng này chịu trách nhiệm cho bao nhiêu bộ nhớ".
- Private Dirty: Đây là RAM mà chỉ quy trình của bạn đang sử dụng và đã được sửa đổi (hoặc là bộ nhớ ẩn danh). Đây là con số quan trọng nhất để tìm rò rỉ vì không thể loại bỏ bộ nhớ này.
- Private Clean: RAM mà chỉ quy trình của bạn đang sử dụng, nhưng là bản sao chưa sửa đổi của một tệp trên bộ nhớ (như tệp DEX). Hệ điều hành có thể loại bỏ trạng thái này nếu bộ nhớ sắp hết.
- SwapPss: Bộ nhớ đã được nén và hoán đổi vào ZRAM. Điều này có thể xảy ra do hệ thống đang chịu áp lực về bộ nhớ hoặc do Android Runtime đã xác định được các tiến trình nền cần thu gọn (di chuyển các trang không dùng đến hoặc không hoạt động sang ZRAM). Trong ví dụ về
dumpsys, hầu hếtNative HeapvàDalvik Heapđã được chuyển sang ZRAM.
- Tóm tắt về ứng dụng: Phân loại ở cấp cao hơn, kết hợp các hàng chi tiết thành các nhóm dễ hiểu.
- Đối tượng: Hữu ích cho việc theo dõi các thực thể
ActivitiesvàViewRootImplbị rò rỉ. Nếu bạn thoát khỏi ứng dụng và buộc GC, thì số lượngActivitiessẽ trở về 0.
Mục ngân sách bộ nhớ
Trong Android 18 trở lên, dumpsys meminfo cũng in phần Ngân sách bộ nhớ khi kết xuất một ứng dụng. Phần này đọc số liệu thống kê memcg của quy trình và của toàn bộ ứng dụng (gói), vì vậy, bạn không cần phải tự tìm các tệp cgroup. Sau đây là phần của ứng dụng mẫu MemoryLab ở nền trước, sau 3 lần nhấn vào Allocate Java Memory (10MB) (Phân bổ bộ nhớ Java (10 MB)):
Memory Budget
Process(KB) Package(KB)
----------- -----------
Current Usage: 83820 87976
Limit: none 4718592
Limit Source: none MemoryLimiter
Peak Usage: 85136 89292
Swap Used: 0 0
Usage Breakdown
Anonymous: 81700 81700
File: 96 4200
File Mapped: 16 616
Shared Memory: 16 16
Kernel: 1580 1632
Page Tables: 640 640
Slab: 439 458
Pressure & Reclaim
Throttle Events: 0 0
Refaults (Anon): 0 0
Refaults (File): 0 0
- Mức sử dụng hiện tại: Bộ nhớ được tính vào memcg (
memory.current). Đây là số lượng mà ngân sách bộ nhớ giới hạn. Trong mẫu, giá trị này tăng khoảng 36.700 KB sau ba lần phân bổ Java 10 MB, phù hợp với TOTAL PSS (TỔNG PSS). Tổng số gói cao hơn tổng số quy trình ngay cả đối với ứng dụng một quy trình khi các trang tệp đã lưu vào bộ nhớ đệm từ lần chạy trước chưa được thu hồi. - Giới hạn: Ngưỡng thu hồi (
memory.high) áp dụng ngay bây giờ hoặcnonenếu không có ngưỡng thu hồi. Trong mẫu, gói có giới hạn bộ nhớ hệ thống và quy trình không có giới hạn riêng. - Nguồn giới hạn: Nguồn gốc của giới hạn.
AndroidManifestlà một<memory-budget>được khai báo trong tệp kê khai ứng dụng,MemoryBudgetManagerlà ngân sách mà ứng dụng đặt tại thời gian chạy,MemoryLimiterlà giới hạn bộ nhớ hệ thống vàam memory-limiterlà chế độ ghi đè thủ công được đặt bằngadb. - Mức sử dụng cao nhất: Mức phí cao nhất kể từ khi memcg được tạo (
memory.peak). Giá trị này cho thấyN/Atrên các hạt nhân không hỗ trợ. - Bộ nhớ hoán đổi đã sử dụng: Bộ nhớ được nén vào ZRAM (
memory.swap.current). - Usage Breakdown (Thông tin chi tiết về mức sử dụng): Các trường chính của
memory.stat, chẳng hạn như bộ nhớ ẩn danh và bộ nhớ được hỗ trợ bằng tệp. - Áp lực và thu hồi: Sự kiện điều tiết đếm số lần memcg vượt quá giới hạn
memory.high. Lỗi trang lặp lại tính số trang đã được hoán đổi hoặc loại bỏ rồi sau đó lại cần đến. Nếu các con số này tăng lên trong khi bạn sử dụng ứng dụng, thì giới hạn sẽ chặt chẽ hơn so với tập hợp đang hoạt động của ứng dụng.
Với cờ -a hoặc -v, phần này cũng liệt kê mọi bộ đếm thô từ memory.stat và memory.events.
Bài tập thực hành: phân loại nhanh bằng meminfo
Bài tập này cho thấy cách các loại phân bổ khác nhau xuất hiện trong meminfo.
Mở ứng dụng:
adb shell am start -W -n com.android.memorylab/.MainActivityKiểm tra bộ nhớ cơ sở:
adb shell dumpsys meminfo -s com.android.memorylab(Cờ
-scung cấp thông tin tóm tắt ngắn gọn về các danh mục bộ nhớ).Allocate Java Memory (Phân bổ bộ nhớ Java): Nhấn vào nút Allocate Java Memory (10MB) (Phân bổ bộ nhớ Java (10 MB)) 3 lần trong ứng dụng.
Quan sát thay đổi: Chạy lại
meminfo -s. Bạn sẽ thấy giá trị Java Heap (Vùng nhớ khối xếp Java) tăng khoảng 30.000 KB (30 MB).Allocate Native Memory (Phân bổ bộ nhớ gốc): Nhấn vào nút Allocate Native Memory (1GB Incompressible) (Phân bổ bộ nhớ gốc (1GB không nén được)) một lần.
Quan sát thay đổi: Chạy lại
meminfo -s. Giá trị Native Heap (Vùng nhớ khối xếp gốc) giờ đây sẽ tăng lên đáng kể (~1.000.000 KB).So sánh với memcg (Android 18 trở lên): Xem phần Memory Budget (Ngân sách bộ nhớ) ở cuối kết quả của
meminfo -s. Mức sử dụng hiện tại tăng lên cùng với TỔNG PSS sau mỗi lần phân bổ, vì cả mức phân bổ Java và mức phân bổ gốc đều được tính vào memcg của ứng dụng.
Chế độ xem toàn cầu
Để xem mức sử dụng bộ nhớ trên toàn bộ hệ thống, hãy chạy lệnh:
adb shell dumpsys meminfo
Lệnh này cung cấp thông tin tóm tắt về mức sử dụng RAM, bao gồm tổng RAM, RAM trống và danh sách các quy trình được sắp xếp theo PSS.
Các chỉ số chính của hệ thống
- ZRAM: Hoán đổi nén trong RAM.
- DMA-BUF: Đồ hoạ và vùng đệm bộ nhớ dùng chung. Các phiên bản Android hiện đại sử dụng
DMA-BUF rộng rãi để kết xuất giao diện người dùng. Nếu tổng số
DMA-BUFrất cao, hãy điều tra các quy trình có đồ hoạ nặng bằng cách sử dụng/sys/kernel/debug/dma_buf/bufinfo. - RAM bị mất: Bộ nhớ không được nhân hệ điều hành hoặc bất kỳ quy trình nào tính đến (ví dụ: vùng dành riêng cho phần cứng).
số liệu thống kê về memcg
Mỗi quy trình ứng dụng đều có nhóm kiểm soát bộ nhớ (memcg) riêng và nhân hệ điều hành sẽ liên tục tính tổng bộ nhớ được tính phí cho nhóm đó (xem memcg để biết cách lưu giữ hồ sơ này khác với RSS và PSS như thế nào). Việc đọc các tệp này không tốn nhiều tài nguyên và không làm gián đoạn ứng dụng, vì vậy, bạn có thể lấy mẫu mỗi giây trong khi chạy một luồng người dùng.
Tra cứu đường dẫn memcg của một quy trình từ dòng 0:: của /proc/<PID>/cgroup thay vì tự tạo đường dẫn, vì bố cục cgroup có thể khác nhau giữa các thiết bị. Truyền từng lệnh thiết bị đến adb shell dưới dạng một chuỗi được trích dẫn để vỏ thiết bị thấy dấu ngoặc kép:
PID=$(adb shell pidof -s <package_name>)
CG=/sys/fs/cgroup$(adb shell "sed -n 's/^0:://p' /proc/$PID/cgroup")
adb shell "cat $CG/memory.current $CG/memory.swap.current"
adb shell "grep -E '^(anon|file|workingset_refault_.*) ' $CG/memory.stat"
Các tệp chính là:
memory.current: Bộ nhớ được tính cho quy trình, tính bằng byte. Đây là số lượng mà ngân sách bộ nhớ giới hạn.memory.swap.current: Bộ nhớ được nén vào ZRAM, tính bằng byte.memory.stat: Thông tin chi tiết về mức phí, tính theo byte cho kích thước.anonlà bộ nhớ ẩn danh, chẳng hạn như vùng nhớ khối xếp Java và vùng nhớ khối xếp gốc.filelà bộ nhớ đệm trang được hỗ trợ bằng tệp, chẳng hạn như mã và tài nguyên.workingset_refault_anonvàworkingset_refault_fileđếm số trang đã được hoán đổi hoặc loại bỏ và sau đó cần dùng lại. Số lượng lỗi tham chiếu tăng lên có nghĩa là quy trình này sắp hết bộ nhớ.memory.events: Số lượng sự kiện giới hạn bộ nhớ.highđếm số lần quy trình bị điều tiết do vượt quá giới hạnmemory.high.
Thư mục mẹ của memcg quy trình thuộc về toàn bộ ứng dụng (UID của ứng dụng). Các tệp của nó bao gồm bộ nhớ của tất cả các quy trình đang chạy của ứng dụng, cộng với các trang tệp được lưu vào bộ nhớ đệm còn sót lại từ các quy trình trước đó của ứng dụng đã thoát và chưa được thu hồi:
adb shell "cat $CG/../memory.current"
Nếu bạn gặp lỗi về quyền, hãy chạy adb root rồi thử lại. Một số thiết bị chỉ
cho phép bạn đọc các tệp này dưới dạng thư mục gốc.
Để xem hướng dẫn từng bước về các tệp này bằng ứng dụng mẫu MemoryLab, hãy xem phần Bài tập thực hành: khám phá memcg.
showmap
Để xem thông tin chi tiết hơn về ánh xạ bộ nhớ của một quy trình, hãy sử dụng showmap. Công cụ này đọc từ /proc/<pid>/smaps.
adb shell showmap <pid>
showmap liệt kê mọi bản đồ bộ nhớ trong quy trình. Các mối liên kết khoá cần tìm bao gồm:
[anon:scudo:primary]hoặc[anon:libc_malloc]: Vùng nhớ khối xếp gốc. Các phiên bản Android hiện đại sử dụng trình phân bổ Scudo.*.art: Hình ảnh vùng nhớ khối xếp Java (ví dụ: hình ảnh khởi động).[anon:dalvik-main space]: Vùng nhớ khối xếp Java chính.*.so: Thư viện chia sẻ.*.{oat,dex,odex,vdex}: Mã DEX đã biên dịch.
Bạn có thể sắp xếp kết quả để tìm ra những đối tượng tiêu thụ bộ nhớ riêng tư lớn nhất:
# Sort by private clean + private dirty memory
adb shell showmap <pid> | awk '{print $6+$7, $0}' | sort -rn | head -n 10
Kết quả mẫu:
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
-------- -------- -------- -------- -------- -------- -------- -------- -------- --------- --------- --------- -------- -------- -------- ---- ------------------------------
2776 18176 5784 2823 0 3008 308 2468 4700 4700 0 0 0 0 0 0 47 [anon:scudo:primary]
560 952 876 657 316 0 560 0 12 12 0 0 0 0 0 0 8 /data/app/.../base.apk
448 393216 448 448 0 0 0 448 0 0 0 0 0 0 0 0 2 [anon:dalvik-main space]
...
Bài tập thực hành: tìm hiểu sâu về showmap
Tìm hiểu cách xác định các mối liên kết bộ nhớ cụ thể và phân biệt giữa bộ nhớ sạch và bộ nhớ bẩn.
- Mmap Clean Memory (Bộ nhớ sạch Mmap): Nhấn vào nút Mmap Clean Anon Memory (10MB) (Bộ nhớ ẩn danh sạch Mmap (10 MB)) trong ứng dụng MemoryLab một lần.
Lấy mã định danh tiến trình: Giao diện người dùng của ứng dụng cho biết mã định danh tiến trình của ứng dụng. Bạn cũng có thể tìm thấy tệp này bằng lệnh sau:
adb shell pidof com.android.memorylabKiểm tra bằng showmap:
# Replace <PID> with the actual process ID from the previous step adb shell showmap <PID> | grep "memorylab-clean"Xác định mối liên kết: Bạn sẽ thấy một mối liên kết ẩn danh có nhãn
[anon:memorylab-clean]với kích thước ảo chính xác là 10240 KB. Lưu ý rằng giá trị private dirty cho bản đồ này có thể là 0. Đây là bộ nhớ ẩn danh "sạch" vì chúng ta đã sử dụngmmapđể đặt trước không gian ảo nhưng chưa ghi vào đó (vì vậy, không có RAM vật lý nào thực sự được sử dụng).So sánh vùng nhớ khối xếp gốc: So sánh vùng nhớ này với các liên kết
[anon:scudo:primary]sẽ xuất hiện trongshowmapnếu bạn chạy vùng nhớ này sau bước 5 của bài tậpmeminfo. Vì mức phân bổ gốc 1 GB đó thực sự ghi vào mọi trang, nên các trang đó là "bị bẩn" và sẽ xuất hiện nhiều trong cột private dirty (bị bẩn riêng tư).Mẹo nâng cao (Đối tượng lớn): Lưu ý các lượt phân bổ Vùng nhớ khối xếp Java trong bài tập
meminfo. Vì là mảng byte 10 MB, nên các mảng này có thể xuất hiện trongshowmapdưới dạng[anon:dalvik-free list large object space]. Android Runtime (ART) đặt các đối tượng lớn hơn 12 KB vào một Không gian đối tượng lớn (LOS) riêng biệt để tránh phân mảnh trong vùng nhớ khối xếp chính.
procstats
Trong khi meminfo cung cấp thông tin tổng quan nhanh theo thời gian, thì procstats cung cấp dữ liệu trong quá khứ bằng cách lấy mẫu định kỳ trạng thái và mức sử dụng bộ nhớ của tất cả ứng dụng đang chạy. Đây là công cụ chính dùng để theo dõi tình trạng bộ nhớ dài hạn và là công cụ thúc đẩy báo cáo mức sử dụng bộ nhớ trong phần Cài đặt Android.
# View stats for the last 3 hours
adb shell dumpsys procstats --hours 3
Tìm hiểu về các trạng thái
Kết quả của procstats có thể rất nhiều. Phần quan trọng nhất là các chỉ số trong ngoặc đơn bên cạnh mỗi trạng thái quy trình:
Top: 100% (54MB-64MB-67MB / 49MB-58MB-61MB / 110MB-120MB-130MB over 4)
Định dạng là: (% thời gian ở trạng thái) (minPss-avgPss-maxPss / minUss-avgUss-maxUss / minRss-avgRss-maxRss trên các mẫu)
- Pss (Kích thước cài đặt theo tỷ lệ): Bộ nhớ dùng chung được chia theo tỷ lệ giữa các quy trình.
- Uss (Kích thước tập hợp duy nhất): Bộ nhớ chỉ được sử dụng bởi quy trình này (tương đương với Private Clean + Private Dirty).
- Rss (Kích thước cài đặt thường trú): Tổng bộ nhớ thực được sử dụng, bao gồm tất cả các trang được chia sẻ (có xu hướng tính thừa).
Lý do điều này quan trọng:
- Ứng dụng ổn định: giá trị tối thiểu/trung bình/tối đa gần nhau.
- Rò rỉ bộ nhớ: mức tối đa cao hơn đáng kể so với mức tối thiểu và mức trung bình tăng dần theo thời gian khi số lượng mẫu tăng lên.
- Mức sử dụng tăng đột biến: mức tối đa cao hơn nhiều so với mức trung bình, cho thấy các hoạt động nặng tạm thời (như xử lý hình ảnh) có thể gây ra lỗi OOM nếu xảy ra không đúng thời điểm.
Trạng thái quy trình
procstats theo dõi các ứng dụng trên nhiều tiểu bang (thường được viết tắt):
- Duy trì: Các quy trình hệ thống quan trọng luôn chạy.
- Trên cùng: Ứng dụng hiện đang chạy trên nền trước và người dùng có thể nhìn thấy.
- ImpFg (Nền trước quan trọng): Các tiến trình nền quan trọng (như IME) hoặc ứng dụng có dịch vụ nền trước.
- ImpBg (Tiến trình nền quan trọng): Các tiến trình nền được coi là quan trọng đối với hệ thống.
- Fgs / BFgs: Dịch vụ nền trước hoặc Dịch vụ nền trước liên kết.
- Service / ServRst: Các dịch vụ nền hoặc dịch vụ hiện đang khởi động lại.
- Cached / LastAct: Các ứng dụng được lưu vào bộ nhớ đệm trong nền để khởi động lại nhanh. Hệ thống có thể loại bỏ các quy trình này một cách an toàn.
Trạng thái bộ nhớ thiết bị và số liệu thống kê về thời gian chạy
procstats cũng theo dõi lượng thời gian mà toàn bộ thiết bị phải chịu áp lực về bộ nhớ. Tìm mục Run time Stats (Số liệu thống kê về thời gian chạy):
Run time Stats:
SOff/Norm: +1h10m15s
SOn/Norm: +15m20s
Low: +2m10s
Crit: +45s
- SOff/SOn: Tắt màn hình so với Bật màn hình.
- Norm / Mod / Low / Crit: Các mức áp lực bộ nhớ (Bình thường, Trung bình, Thấp, Nghiêm trọng).
Nếu một thiết bị dành nhiều thời gian ở trạng thái Thấp hoặc Nghiêm trọng, thì thiết bị đó sẽ có cảm giác chậm chạp vì nhân hệ điều hành sẽ tích cực lấy lại bộ nhớ và Trình tắt ứng dụng khi bộ nhớ thấp (LMK) bắt đầu chấm dứt các quy trình trong nền. Sau đó, bạn có thể xem số liệu thống kê theo từng gói để biết ứng dụng nào có mức sử dụng bộ nhớ cao, đặc biệt là khi thiết bị ở trạng thái căng thẳng.
← Khái niệm | ↑ Lên | Java →