Leak memory trong Android (Phần 1)

Xin chào mọi người !!!!

Bài viết này mình xin chia sẻ về một vấn đề rất thường gặp trong lập trình Android nói riêng và Java nói chung là Leak Memory.

Vậy Leak Memory là gì?

Hãy dạo qua sơ lược về khái niệm Leak Memory là gì nhé !!

memleak.jpg

Hãy bất đầu với sự so sánh cơ chế quản lý bộ nhớ giữa ngôn ngữ lập trình C và ngôn ngữ lập trình Java.

Trong C, khi một lập trình viên khai báo một biến thì phải tự phân bổ kích thước memory được sử dụng và phải tự giải phóng memory thủ công sau khi sử dụng. Đối với Java thì khác, khi bạn khai báo một kiểu new Interger(1), bạn không cần phải cấp phát bộ nhớ cho nó, đó là công việc được xử lý bởi Java Virtual Machine (JVM).

Sau đó, trong vòng đời của ứng dụng, JVM sẽ thường xuyên kiểm tra, xác định rằng object nào trong memory đang sử dụng hoặc không sử dụng và những object nào không sử dụng sẽ bị thu hồi lại tài nguyên đã cấp phát cho nó. Quá trình này được gọi là thu góp rác hay còn được gọi là Garbage Collector (GC) .

playmobil-771313_960_720.jpg

Quá trình quản lý tự động bộ nhớ của Java hoàn toàn dựa trên GC - quá trình kiểm tra phát hiện những object nào không còn được sử dụng và lấy lại tài nguyên đã cấp phát của nó.

Và như thế, bạn có thể hiểu nôm na Memory Leak có nghĩa là GC sẽ không thể thu hồi lại những vùng memory đã cấp phát cho một object unused ( không còn được sử dụng ).

Mỗi ứng dụng sẽ được cấp phát một lượng memory nhất đinh, và những object unused nhưng không được thu hồi bộ nhớ dần dần sẽ tăng lên dẫn tới tràn bộ nhớ.

Vì vậy một ngày đẹp trời, ứng dụng của bạn có thể quăng ra lỗi OutOfMemoryException bất cứ lúc nào.

Screenshot from 2016-10-22 10:55:19.png

OutOfMemoryException theo mình là một lỗi cực kỳ nguy hiểm, khi nó sẽ dẫn tới việc ứng dụng của bạn crash và buộc phải khởi động lại. Lỗi này có thể tránh được nếu chúng ta hiểu rõ và nắm chắc về việc quản lý bộ nhớ trong vòng đời của ứng dụng, điều mà chúng ta sẽ cùng nhau tìm hiểu trong các loạt bài về Leak Memory.

Vậy chúng ta đã tạm hiểu rõ Leak Memory là gì rồi đúng không nào? Bước tiếp theo chúng ta sẽ cùng nhau tìm hiểu về Garbage Collector (GC).

Garbage Collector (GC) giúp Java quản lý memory ra sao và nó hoạt động như thế nào?

Garbage Collector (GC)

Rất nhiều lập trình viên nghĩ rằng, Garbage Collector thu nhập và loại bỏ những object unused (dead object). Nhưng trong thực tế, điều đó hoàn toàn ngược lại.

Những object đang được sử dụng (live object) sẽ được track (theo dõi) bởi GC, và những object nằm ngoài sự theo dõi của GC được coi là những dead object hay còn gọi là object rác (cần được thu hồi tài nguyên). Sự nhầm lẫn trên có thể dẫn tới rất nhiều vấn đề về hiệu năng (performance) của ứng dụng.

Một sự nhầm lẫn khác nữa về vấn đề cấp phát và thu hồi bộ nhớ của GC. Nhiều lập trình viên nghĩ rằng GC sẽ thu hồi memory của những dead object và trả lại memory về cho hệ điều hành. Điều này thực chất chưa đúng. Vì sao? Hãy nói một chút về bộ nhớ HeapStack trong Java.

Java Heap và Stack Memory

Java-Heap-Stack-Memory.png

Java HeapStack Memory là một phần của bộ nhớ được JVM sử dụng để chạy chương trình Java của bạn.

Khi bạn chạy chương trình Java, JVM sẽ yêu cầu hệ điều hành cấp cho một không gian bộ nhớ trong RAM để dùng cho việc chạy chương trình. JVM sẽ chia bộ nhớ được cấp phát này thành 2 phần: HeapStack cho việc quản lý.

Phương pháp lý bộ nhớ của HeapStack Memory là hoàn toàn khác nhau. Bộ nhớ Stack tồn tại trong thời gian ngắn, nhưng bộ nhớ Heap tồn tại từ lúc ứng dụng bắt đầu thực thi đến lúc kết thúc

Đối với bộ nhớ Stack, các vùng nhớ của những biến dữ liệu nguyên thủy trong Stack sẽ được tự động giải phóng sau khi khi gọi hàm.

Bất cứ khi nào gọi 1 hàm, một khối bộ nhớ mới sẽ được tạo trong Stack cho hàm đó để lưu các biến local. Khi hàm thực hiện xong, khối bộ nhớ cho hàm sẽ bị xoá, và giải phóng bộ nhớ trong Stack.

Đối với bộ nhớ Heap thì hoàn toàn khác, Bộ nhớ Heap trong Java được dùng để cấp phát bộ nhớ cho các đối tượng, các lớp JRE lúc thực thi. Bất cứ khi nào, chúng ta tạo đối tượng, nó sẽ được tạo trong bộ nhớ Heap. Với những đối tượng không còn được tham chiếu nữa thì trình thu thập rác (Garbage Collection) sẽ giải phóng bộ nhớ mà các đối tượng đó sử dụng. Vậy những vùng nhớ được giải phóng có được trả lại cho hệ điều hành hay không? Câu trả lời là không.

Mỗi khi ứng dụng thực thi, nó sẽ được cấp phát một vùng nhớ cố định. Giả sử vùng nhớ được chia thành 6 phần nhỏ, khi một object mới được taọ ra, nó sẽ chiếm lấy 1 phần, vậy số vùng nhớ còn lại là 5. Sau đó, object đó được giải phóng, thì vùng nhớ nó được cấp phát sẽ trả lại cho bộ nhớ heap (6 phần) chứ không phải trả lại cho hệ điều hành. Quá trình này cơ bản là lấy lại vùng nhớ để sẵn sàng cung cấp cho những object khác, chứ không phải lấy lại vùng nhớ cho hệ điều hành.

allocation.png

Vậy làm sao để biết application bị leak memory?

Câu trả lời là làm nhiều, chết nhiều thì biết thôi 😃). Ngoài ra còn có rất nhiều phần mềm giúp hỗ trợ phát hiện leak memory.

Giả sử phần mềm của bạn đã gần tới lúc release thì sẽ rất tốn công nếu đi mò mẫm từng class, từng dòng code để phát hiện leak memory. Vậy nên bạn có thể sử dụng phần mềm hỗ trợ giúp bạn nhanh chóng phát hiện chỗ nào bị memory leak mà giết nó trước khi đưa cho những tester sát thủ

AAEAAQAAAAAAAAijAAAAJDA1YTRhMTg4LTdiMTAtNDI0Ni1iYTdlLTBkNmY0MjBjYmU0MQ.jpg

Đối với Android, mình xin giới thiệu các bạn một thư viện phát hiện Leak Memory cực kỳ hiệu quả đó là LeakCanary.

screenshot.png

Bạn chỉ việc cài đặt và cấu hình, xong chạy app, play around vài vòng, đi qua vài con đường activity, thỉnh thoảng vọt lên notification và nếu những chỗ nào có khả năng gây ra leak memory thì Leak Canary sẽ thông báo cho bạn. Xong thì chúng ta bay vào sửa thôi, rất dễ dàng.

Tạm xong phần 1 nhé, phần sau chúng ta sẽ đi chuyên sâu về cách sử dụng Leak Canary và một số trường hợp gây leak memory kinh điển trong Java.

P/s: Bài viết có sử dụng tư liệu trên mạng (hình ảnh, kiến thức ...) 😃


All Rights Reserved