Mỗi khi ứng dụng của bạn tạo một luồng mới, hệ điều hành phải phân bổ bộ nhớ để hỗ trợ luồng đó. Mặc dù các luồng thường được coi là "nhẹ" so với các quy trình đầy đủ, nhưng chi phí bộ nhớ của chúng không phải bằng không. Rò rỉ luồng là một cách rất phổ biến gây rò rỉ bộ nhớ trên Android.
Chi phí của một luồng
Khi một luồng được tạo trên Android, có hai thành phần chính tiêu thụ bộ nhớ:
- Ngăn xếp gốc: Thư viện
pthreadphân bổ một ngăn xếp cho luồng để lưu trữ các biến cục bộ và khung lệnh gọi. Trên Android, kích thước này thường là 1 MB theo mặc định. - Cấu trúc JVM/Hạt nhân nội bộ: ART và hạt nhân Linux phải phân bổ cấu trúc dữ liệu (như đối tượng
pthread_internal_tvàjava.lang.Thread) để theo dõi và quản lý luồng.
Bộ nhớ ngăn xếp và mmap
Điều quan trọng là bạn cần hiểu rằng Android phân bổ ngăn xếp gốc 1 MB bằng cách sử dụng mmap với cờ MAP_ANONYMOUS | MAP_NORESERVE.
Điều này có nghĩa là hệ điều hành dự trữ 1 MB không gian địa chỉ bộ nhớ ảo, nhưng không phân bổ ngay 1 MB RAM vật lý. Bộ nhớ vật lý chỉ được phân bổ (phân trang) khi luồng thực sự thực thi mã và đẩy dữ liệu vào ngăn xếp. Một luồng không làm gì sẽ tiêu thụ rất ít RAM vật lý cho ngăn xếp của nó.
Bài tập thực hành: rò rỉ luồng
Chúng ta sẽ sử dụng ứng dụng mẫu MemoryLab để quan sát điều gì xảy ra khi một ứng dụng tạo ra hàng trăm luồng rảnh rỗi.
1. Ra mắt và đường cơ sở
Chạy ứng dụng và đọc bộ nhớ cơ sở:
adb shell am start -W -n com.android.memorylab/.MainActivity
adb shell dumpsys meminfo -s com.android.memorylab
Xem các hàng Stack và Native Heap trong kết quả. Những thay đổi này có thể sẽ không đáng kể.
2. Tạo 100 chuỗi bài đăng
Nhấn vào nút Create 100 Threads (Tạo 100 chuỗi). Ứng dụng sẽ tạo ra 100 luồng mới. Để đảm bảo bộ nhớ ngăn xếp thực sự được phân trang vào RAM vật lý (để chúng ta có thể thấy bộ nhớ đó trong các công cụ), mỗi luồng sẽ phân bổ và ghi vào một mảng 10 KB nhỏ trên ngăn xếp trước khi bắt đầu chờ.
Chạy lại lệnh meminfo trong khi các luồng đang hoạt động:
adb shell dumpsys meminfo -s com.android.memorylab
Kết quả:
So sánh kết quả mới với kết quả cơ sở. Bạn sẽ nhận thấy mức tăng đáng kể trong khi các luồng đang hoạt động:
Stack: Tăng thêm 1 megabyte trở lên. Điều này thể hiện các trang thực tế được phân bổ cho 100 ngăn xếp luồng (mỗi ngăn xếp chạm ít nhất 10 KB).Native Heap/Private Other: Tăng lên do cấu trúcpthreadnội bộ được hệ thống phân bổ cho mỗi luồng.
Để dọn dẹp, hãy nhấn vào nút Destroy All Threads (Huỷ tất cả luồng).
3. Xem các luồng trong Perfetto
Perfetto là công cụ tuyệt vời để theo dõi vòng đời và số lượng luồng theo thời gian.

PerfettoSQL để phân tích luồng
Bạn có thể truy vấn dấu vết để đếm chính xác số luồng do ứng dụng tạo ra:
SELECT p.name AS process_name, COUNT(t.id) as thread_count
FROM thread t JOIN process p USING (upid)
WHERE p.name = 'com.android.memorylab' AND t.name LIKE 'LeakedThread-%';
Bạn có thể dùng một truy vấn như thế này để tạo một bản gỡ lỗi có bộ đếm như trong ảnh chụp màn hình.
← Mã ứng dụng | ↑ Lên | Địa phương →