Out Of Memory trên game mobile: Đừng chỉ nhìn GC Alloc
Minh Khoa
Tác giả
Game chạy ổn trên máy mạnh. Sang máy yếu, chơi khoảng 15 phút thì văng thẳng ra màn hình chính. Không exception, không stack trace C#.
Phản xạ đầu tiên thường là: “Chắc code sinh nhiều rác quá.”
Có thể. Nhưng nếu chỉ nhìn GC.Alloc, mình đang bỏ sót một phần lớn bộ nhớ của game.
Cũng không nên khẳng định “90% OOM đến từ Native Memory”. Không có tỷ lệ chung cho mọi project. Managed, native và tài nguyên đồ họa đều có thể góp phần khiến game vượt ngân sách bộ nhớ.
Bộ nhớ đang nằm ở đâu?
Unity phân biệt ba lớp quản lý bộ nhớ:
| Lớp | Ví dụ | Ai thu hồi? |
|---|---|---|
| Managed | string, object C#, dữ liệu trong array, List | GC, khi không còn tham chiếu |
| C# Unmanaged | Buffer của NativeArray, NativeList | Theo vòng đời allocator; thường cần Dispose() |
| Native của engine | Texture, mesh, audio, render target, buffer engine/plugin | Theo vòng đời tài nguyên và API tương ứng |
Đây là cách phân loại theo quản lý bộ nhớ, không phải ba thanh RAM riêng biệt. Unity: Memory overview
Một List<byte[]> giữ dữ liệu mãi vẫn có thể làm managed heap phình lớn. GC không thu hồi được thứ code còn giữ tham chiếu.
Và GC.Alloc chỉ cho biết lượng managed memory được cấp phát trong frame, không phải tổng RAM game đang giữ. Thấy 0 B/frame chưa đủ để kết luận game dùng ít bộ nhớ.
Vì sao không có log C#?
Khi hệ điều hành chấm dứt process vì áp lực bộ nhớ, game không có cơ hội xử lý như một exception thông thường.
Android có cơ chế LMK, lựa chọn process dựa cả vào mức ưu tiên. iOS có Jetsam và báo cáo riêng, không chứa stack trace như crash report thông thường. Có thể kiểm tra ApplicationExitInfo trên Android và Jetsam report trên iOS. Android, Apple
Tuy nhiên, không có log C# chưa chứng minh đó là OOM. Native crash cũng có thể biểu hiện tương tự; cần xác nhận bằng log hệ điều hành trước.