Android Memory Leaks - Android Mastery by Dan Tech
Android Memory Leaks - Android Mastery by Dan Tech

Android App Memory: Kiến Thức cần biết

Memory là 1 topic quan trọng bậc nhất trong công việc lập trình, đặc biệt là Android. Hãy cùng mình tìm hiểu sâu hơn nhé!

App Memory trong Android

Bộ nhớ RAM của Android chia làm 2 phần chính StackHeap

Stack

Stack là nơi lưu trữ các biến tạm khi chương trình được chạy. Các biến này sẽ được giải phóng hoàn toàn khi chương trình thoát ra khỏi các hàm, các vòng lặp, hoặc các vòng điều kiện.

fun foo() {
	var i = 10 // Stack memory
	var o = Object() // o is on Stack, Object is on Heap
}

Kích thước của Stack rất nhỏ (vài MB) nên việc sử dụng hết sức hạn chế. Khi bạn sử dụng hết bộ nhớ Stack, chương trình sẽ gặp lỗi Stack Overflow.

Phần lớn dữ liệu chương trình sẽ được cấp phát trên Heap.

Heap

Heap là phân vùng dùng để lưu trữ các Object trong chương trình Android của bạn. 1 cách dễ hiểu thì Heap là nơi lưu trữ các dữ liệu được tạo ra sau chữ new (đối với Java) hoặc các Object được tạo ra (đối với Kotlin)

fun f() {
	val point = Point(2, 5) // Point(2,5) is stored on Heap
}

Mặc dù Heap là phân vùng lớn hơn Stack rất nhiều nhưng Android vẫn giới hạn dung lượng. Khi việc sử dụng Memory đạt đến cực ngưỡng của Heap, chương trình sẽ throws một OutOfMemoryError

Garbage Collector

Garbage Collector (GC) là 1 tính năng trong các ngôn ngữ hiện đại (Java, Kotlin, Go..). GC được quyền thu hồi lại các vùng nhớ trên Heap khi chúng không còn được reference bởi các biến khác.

fun sample() {
	var object = Object("a") // object references to Object("a")
	var object = null // object reference to null Object("a") now is a free memory, 
	// Object("a") could be collected by GC in some time in the future
}

Memory Leaks

Memory Leak là hiện trạng một biến hoặc một phần bộ nhớ trên Heap mặc dù không còn được dùng cho mục đích chạy chương trình nữa nhưng vẫn chưa được GC thu hồi. Điều này gây lãng phí tài nguyên máy, và dễ dẫn đến OutOfMemoryError

Lý do cho việc này chung quy là do các biến không còn được sử dụng bởi chương trình nữa nhưng lại không thể được GC thu hồi vì chúng vẫn còn được reference từ một nơi nào đó.

Vậy nơi nào đó là nơi nào?

Nhắc lại kiến thức cũ: Tất cả mọi thứ được khai báo trong chương trình đều có vòng đời của nó (Lifecycle)

Việc một vùng nhớ trên Heap (gọi là vùng nhớ A) khi không còn nhu cầu sử dụng nữa, nhưng vẫn bị reference bởi một biến khác (gọi là biến B) . Chứng tỏ rằng vòng đời của biến B dài hơn của vùng nhớ A. Chính vì vậy mà B chưa được thu hồi, làm cho vùng nhớ A cũng không thể được GC thu hồi.

Để cụ thể hơn hãy lấy ví dụ

binding?.buttonSimulateMemoryLeak?.setOnClickListener {
    // obviously this is a memory leak
    // instance is a static variable declared in somewhere
    instance = this
    Handler(handlerThread3.looper).post {
        // simulate memory leak
        // this is harder to find
        val fragment = this@SimpleFragment
        while (true) {
            Log.d("SimpleFragment", "Simulate Memory Leak ${System.currentTimeMillis()} $fragment")
            Thread.sleep(1000)
        }
    }
}

Trong ví dụ trên chúng ta start một Thread mà ở đó có 1 vòng lặp vô tận. Trong vòng lặp vô tận này ta có tham chiếu đến 1 Fragment.

Trong trường hợp Fragment được Destroy thì bộ nhớ của Fragment vẫn chưa được GC thu hồi vì nó vẫn còn bị reference từ bên trong Thread.

1 số cách khắc phục cho trường hợp này

  • Ghi nhớ hãy terminate Thread khi không còn nhu cầu sử dụng nữa.
  • Không thực hiện tham chiếu đến các Component có lifecycle ngắn trong 1 Object có lifecycle dài (hoặc không kiểm soát lifecycle)