GC đã chạy, nhưng RAM không giảm. Rác đi đâu rồi?
Minh Khoa
Tác giả
Mở Profiler, gọi GC.Collect(), RAM vẫn cao. Gọi thêm vài lần cho chắc… vẫn vậy.
Khoan kết luận GC không hoạt động. Có thể object đã được thu hồi, nhưng mình đang nhìn vào một con số khác.
Dọn đồ khỏi kho, chưa chắc kho nhỏ lại
GC thu hồi bộ nhớ của những object C# không còn được tham chiếu tới, để chỗ đó có thể dùng cho lần cấp phát tiếp theo. Unity mô tả cơ chế GC.
Ví dụ minh họa:
| Managed Heap | Trước GC | Sau GC |
|---|---|---|
| Dung lượng đang giữ — Reserved | 128 MB | 128 MB |
| Dung lượng object chiếm — Used | 100 MB | 40 MB |
| Chỗ trống có thể tái sử dụng | 28 MB | 88 MB |
GC đã thu hồi được 60 MB. Nhưng heap vẫn giữ 128 MB để phục vụ những lần cấp phát sau.
Giống như dọn bớt hàng trong kho: bên trong thoáng hơn, diện tích kho chưa thay đổi.
Cũng không nên nói Unity “không bao giờ trả bộ nhớ về OS”. Các trang nhớ trống có thể được trả lại trên nhiều nền tảng, nhưng thời điểm không được đảm bảo. Unity Manual.
RAM của game còn nhiều thứ ngoài object C#
Texture, Mesh, Audio, bộ nhớ đồ họa, metadata của runtime… đều góp vào bộ nhớ tiến trình. Chỉ số Reserved trong Unity cũng không đồng nghĩa với RAM vật lý mà hệ điều hành đang ghi nhận. Memory Profiler.
Ví dụ: game vẫn giữ hàng trăm MB texture sau khi thoát màn chơi. Gọi GC.Collect() không thay thế việc release hoặc unload những asset đó.
Ở chiều ngược lại, nếu một static List vẫn giữ các object C#, chúng vẫn được xem là còn sống. GC chạy đúng cũng không thể thu hồi chúng. Cách GC xác định object còn sống.
Kiểm tra thế nào cho đúng?
Mình sẽ bắt đầu bằng hai câu hỏi:
- Managed Used có giảm sau GC không?
Profiler.GetMonoUsedSizeLong()cho biết bộ nhớ object đang chiếm, bao gồm cả rác chưa được thu hồi. - Managed Reserved có giữ nguyên không?
Profiler.GetMonoHeapSizeLong()cho biết dung lượng heap đang giữ. Hai chỉ số này khác nhau là bình thường. .