Rò rỉ bộ nhớ và phình to — nguyên nhân và cách tránh

Tác giả: IT Sectr Đã đăng: 2026-07-28 Thời gian đọc: 10 phút

Rò rỉ bộ nhớ — một trong những vấn đề nguy hiểm nhất trong phát triển di động. Mức sử dụng bộ nhớ của ứng dụng tăng đều đặn cho đến khi đạt đến giới hạn do hệ điều hành đặt ra, sau đó xảy ra OutOfMemoryError hoặc buộc phải kết thúc. Theo Square Engineering, khoảng 40% ứng dụng Android có ít nhất một rò rỉ bộ nhớ chỉ có thể phát hiện qua việc lập hồ sơ. Hãy xem xét nguyên nhân và phương pháp ngăn chặn tăng trưởng bộ nhớ.

Những điểm chính

  • Khả năng truy cập GC — đối tượng không bị xoá nếu có tham chiếu hoạt động từ tập gốc
  • Tham chiếu tĩnh đến Activity hoặc Context — nguyên nhân rò rỉ phổ biến nhất trong Android
  • LeakCanary — công cụ tiêu chuẩn để phát hiện rò rỉ tự động trong Android
  • WeakReference — giải pháp cho các tham chiếu không nên cản trở việc thu gom rác
  • Thành phần nhận biết vòng đời tự động huỷ đăng ký khi view bị phá huỷ

Rò rỉ bộ nhớ và phình to ứng dụng là gì?

Rò rỉ bộ nhớ — tình huống một đối tượng không còn cần thiết cho ứng dụng vẫn được giữ trong heap vì một tham chiếu hoạt động từ tập gốc GC Root vẫn trỏ đến nó. Bộ thu gom rác coi đối tượng đó còn sống và không xoá nó.

Phình to bộ nhớ — một vấn đề rộng hơn khi ứng dụng tiêu thụ nhiều bộ nhớ hơn mức cần thiết để thực hiện các tác vụ hiện tại. Nguyên nhân: lưu đệm quá mức, trùng lặp đối tượng, cấu trúc dữ liệu không tối ưu và phân mảnh heap.

Trong Android, mỗi ứng dụng được cấp một heap giới hạn (thường 64–512 MB tuỳ theo thiết bị và phiên bản OS). Trong iOS, giới hạn ít nghiêm ngặt hơn, nhưng hệ thống gửi cảnh báo bộ nhớ khi đến gần giới hạn.

Đặc điểmAndroidiOS
Giới hạn heap64–512 MB (tuỳ thiết bị)Ngầm định (hệ thống)
Thu gom rácART (Đồng thời, Nhỏ gọn)ARC (Đếm tham chiếu tự động)
Cơ chế rò rỉTham chiếu GC RootChu kỳ lưu giữ (chu kỳ tham chiếu mạnh)
Kết quảOutOfMemoryErrorCảnh báo bộ nhớ → kết thúc

Theo Facebook Engineering Blog, rò rỉ bộ nhớ gây ra ~15% báo cáo sự cố trong ứng dụng di động. Trên Android, điều này còn thêm ANR do các lần tạm dừng GC thường xuyên khi thiếu bộ nhớ.

Các mẫu rò rỉ bộ nhớ phổ biến trong Android và iOS

Tham chiếu tĩnh đến Activity — một rò rỉ kinh điển trong Android. Nếu một trường tĩnh hoặc singleton giữ tham chiếu đến Activity, nó sẽ không được GC thu gom ngay cả sau finish() khi singleton còn sống. Activity là một đối tượng nặng chứa hệ thống phân cấp view, tài nguyên và Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // leak: static reference to Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
    }
}

Lớp ẩn danh và lambda — ngầm giữ tham chiếu đến lớp bên ngoài. Nếu Runnable hoặc Callback được truyền cho dịch vụ bên ngoài và Activity bị phá huỷ, đối tượng lớp ẩn danh vẫn nằm trong hàng đợi và ngăn Activity bị thu gom rác.

  • Handler có độ trễ — nếu Activity bị phá huỷ nhưng Handler.postDelayed chưa được thực thi, Activity bị rò rỉ
  • Thread và AsyncTask — khi xoay màn hình, Activity được tạo lại trong khi Thread cũ vẫn giữ tham chiếu đến Activity cũ
  • Retrofit/Callback — Callback ẩn danh giữ tham chiếu đến presenter hoặc fragment
  • Người quan sát — đăng ký LiveData hoặc RxJava mà không huỷ đăng ký tại onDestroy

Trong iOS, vấn đề chính là chu kỳ lưu giữ: hai đối tượng giữ tham chiếu mạnh đến nhau và ARC không thể đặt bộ đếm tham chiếu về 0 cho cả hai. Một trường hợp điển hình: closure bắt self mạnh và self giữ tham chiếu đến closure.

Làm thế nào để phát hiện rò rỉ bộ nhớ?

LeakCanary — một thư viện từ Square để phát hiện rò rỉ tự động trong Android. Sau khi Activity hoặc Fragment bị phá huỷ, nó kiểm tra xem đối tượng đã được GC thu gom chưa. Nếu chưa, nó tạo heap dump và hiển thị dấu vết rò rỉ.

kotlin
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary auto-installs in debug build
        // via ContentProvider — zero code setup
    }
}

// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — công cụ tích hợp để giám sát bộ nhớ theo thời gian thực. Cho phép ghi heap dump, tìm đối tượng khả nghi (Retained Size > 1 MB) và theo dõi đường dẫn GC root đến từng đối tượng.

Đối với iOS, hãy sử dụng Xcode Memory Graph Debugger. Nó trực quan hoá đồ thị đối tượng trong bộ nhớ, hiển thị chu kỳ lưu giữ và cho phép phát hiện ngay lập tức các tham chiếu vòng. Instruments > Allocations cũng có sẵn để giám sát dài hạn.

Chiến lược phòng ngừa

WeakReference — cơ chế cơ bản cho các tham chiếu không nên can thiệp vào việc thu gom rác. Nếu GC quyết định thu gom một đối tượng, WeakReference trả về null. Nó được sử dụng cho callback, trình lắng nghe và tham chiếu đến thành phần UI từ các luồng nền.

Thành phần nhận biết vòng đời — cách tiếp cận kiến trúc được triển khai trong Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Các đăng ký tự động bị huỷ tại onDestroy, loại bỏ lớp rò rỉ chính.

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // coroutine auto-cancels on onCleared()
        }
    }
}

viewModelScopelifecycleScope — CoroutineScope tích hợp trong Android bị huỷ tại sự kiện vòng đời tương ứng. Điều này loại bỏ rò rỉ qua coroutine — kịch bản phổ biến nhất trong phát triển Android hiện đại.

  • Không sử dụng tham chiếu tĩnh đến Context, Activity, View hoặc Fragment
  • Huỷ tất cả đăng ký RxJava trong disposeBag / CompositeDisposable tại onDestroy
  • Sử dụng [weak self] / [unowned self] trong closures iOS để ngăn chu kỳ lưu giữ
  • Kiểm tra Bitmap và đối tượng lớn — chúng cần được tái chế hoặc đặt thành null

Công cụ lập hồ sơ bộ nhớ

Memory Profiler trong Android Studio — công cụ chính để giám sát heap. Hiển thị cấp phát trực tiếp, ảnh chụp heap và số lượng đối tượng theo loại. Cho phép ghi dump và phân tích trong MAT (Memory Analyzer Tool) để tìm đối tượng khả nghi.

Eclipse MAT — trình phân tích heap dump trên máy tính. Sau khi tải tệp HPROF từ Android Studio, MAT xây dựng cây thống trị, hiển thị kích thước lưu giữ của mỗi đối tượng và cung cấp phân tích rò rỉ tự động qua Leak Suspects Report.

Xcode Memory Graph — trình gỡ lỗi chu kỳ lưu giữ trực quan. Khi nhấp vào nút Memory Graph Debugger, Xcode dừng ứng dụng, xây dựng đồ thị đối tượng hoàn chỉnh trong bộ nhớ và tô sáng chu kỳ lưu giữ bằng màu đỏ.

Công cụNền tảngTính năng
LeakCanaryAndroidTự động phát hiện rò rỉ sau destroy
Memory ProfilerAndroid StudioHeap dump + cấp phát trực tiếp
Eclipse MATAndroidCây thống trị, Leak Suspects Report
Memory GraphiOS (Xcode)Trực quan hoá chu kỳ lưu giữ

Theo Google I/O 2023, các ứng dụng sử dụng LeakCanary trong bản gỡ lỗi giảm sự cố liên quan đến bộ nhớ 30–50% trong 2 tháng đầu sau khi áp dụng. Nên thêm LeakCanary ở giai đoạn khởi tạo dự án.

Câu hỏi thường gặp

Rò rỉ bộ nhớ khác với phình to như thế nào?

Rò rỉ — đối tượng không thể truy cập bằng mã nhưng không được GC thu gom do tham chiếu hoạt động. Phình to — ứng dụng giữ các đối tượng cần thiết về mặt logic nhưng với số lượng quá mức (ví dụ: bộ đệm 50 MB trong ứng dụng đang chạy 80 MB). Phình to được giải quyết về mặt kiến trúc, rò rỉ — thông qua quản lý tham chiếu chính xác.

LeakCanary tìm rò rỉ như thế nào?

LeakCanary sử dụng ObjectWatcher — sau onDestroy() của Activity, nó tạo WeakReference đến Activity và chạy GC. Nếu WeakReference không được xoá sau 5 giây, LeakCanary tạo heap dump, phân tích chuỗi tham chiếu ngắn nhất từ GC Root đến đối tượng và hiển thị stack rò rỉ chính xác kèm tệp và dòng mã.

Tại sao Bitmap thường gây ra OutOfMemoryError?

Bitmap chiếm bộ nhớ bên ngoài heap Java trong bộ nhớ gốc (native heap). Kích thước một Bitmap = chiều rộng × chiều cao × 4 byte (ARGB_8888). Ảnh 12 MP (4000×3000) chiếm 48 MB. Android không phải lúc nào cũng giải phóng bộ nhớ gốc kịp thời, do đó tích luỹ nhiều Bitmap dẫn đến OOM ngay cả khi heap Java đủ.

Chu kỳ lưu giữ trong iOS là gì?

Chu kỳ lưu giữ — tình huống trong ARC khi hai đối tượng giữ tham chiếu mạnh đến nhau và bộ đếm tham chiếu không bao giờ về 0. Ví dụ điển hình: ViewController có tham chiếu mạnh đến closure và closure bắt self mạnh. Giải pháp: sử dụng [weak self] hoặc [unowned self] trong closures.

Kích thước heap tối đa trên Android là bao nhiêu?

Kích thước heap phụ thuộc vào thiết bị và phiên bản Android. Thiết bị cũ (API 15–24) — 64–128 MB. Thiết bị hiện đại (API 25+) — 256–512 MB. Giá trị chính xác có thể lấy qua ActivityManager.getMemoryClass(). Cho ứng dụng lớn (game, trình biên tập), largeHeap=true trong tệp kê khai cung cấp tới 1 GB.

Tóm tắt

  • Rò rỉ bộ nhớ — đối tượng không được GC thu gom do tham chiếu hoạt động từ tập gốc; phình to — tiêu thụ bộ nhớ quá mức không có rò rỉ rõ ràng
  • Tham chiếu tĩnh đến Activity, Context hoặc View — nguyên nhân số một gây rò rỉ trong Android; giải pháp — WeakReference hoặc Application Context
  • Lớp ẩn danh và lambda ngầm giữ tham chiếu đến lớp bên ngoài; callback không bị huỷ là nguyên nhân phổ biến thứ hai
  • LeakCanary — tiêu chuẩn phát hiện rò rỉ tự động trong Android; tích hợp mất 5 phút và giảm tỷ lệ sự cố 30–50%
  • lifecycleScopeviewModelScope tự động huỷ coroutine khi bị phá huỷ, loại bỏ toàn bộ lớp rò rỉ
  • Chu kỳ lưu giữ trong iOS được giải quyết bằng weak/unowned self trong closures và delegate
  • Lập hồ sơ bộ nhớ ít nhất một lần mỗi sprint — heap dump với MAT hoặc Memory Graph nên trở thành một phần của đánh giá mã

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm