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 Stack và Heap
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)