[Memory leak]Mở đầu về memory leak trong java
Đã lâu lắm rồi mình chưa có chia sẻ được chủ đề gì tử tế. Chủ yếu là các bài viết do mình dịch hoặc chia sẻ lại từ blog của người khác Hôm nay nhân dịp hứng thú với chủ để Memory Leak trong Android. Mình sẽ tập chung nghiên cứu về vấn đề này để chia sẻ cùng mọi người. Bài viết này dựa trên sự tổng hợp, thực nghiệm và kinh nghiệm làm việc của cá nhân mình nên chắc hẳn sẽ có sai xót. Hi vọng được sự góp ý của các bạn, chúng ta cùng thảo luận.
Chúng ta chỉ có một lượng giới hạn heap size cho bộ nhớ của điện thoại. Nhất là khi thiết bị di động lại vô cùng khiêm tốn về bộ nhớ, kích thướng màn hình vẫn tăng lên mỗi ngày dẫn đến bitmaps hiển thị cũng tăng theo. Bạn hẳn đã từng gặp phải lỗi OutOfMemoryException và chán ngấy chúng? Đến lúc bạn phải quan tâm tới memory leak rồi.
Thư viện hệ thống đang chiếm phần lớn dung lượng lưu trữ. Cứ thêm mỗi một chút bộ nhớ bạn sử dụng là thêm một đống việc mà GC phải thực hiện, cũng là lúc mà người dùng tăng thêm khoảng thời gian chờ GC xử lí. Đã đến lúc bạn nên hiểu về GC.
Battery life. Đã đến lúc bạn nên thương bộ nhớ trong thiết bị của người dùng.
“Larger heap = larger pause time = poor performance“
I.Tìm hiểu về Garbage Collector
Trong GC có chứa một GC map dạng cây với GC root ở đầu.
Hình 1: Cấu trúc cây của GC Map
Điểm root này sẽ tạo một bản đồ liên kết giữa các đối tượng có trong ứng dụng đang được chạy trên process. Đối tượng đầu tiên được root tham chiếu tới chính là thread.
Hình 2: Quét các điểm để xây dựng GC Map
Khi thực hiện tham chiếu trong GC map, bắt đầu từ root, đánh dấu tất cả các đối tượng có thể với tới được bằng cách sử dụng thuật toán quét điểm depth-first-search. Các đối tượng không được đánh dấu bởi GC được gọi là Garbage và có thể bị GC thu hồi bộ nhớ bất kì lúc nào. Toàn bộ quá trình này GC đã xử dụng thuật toán mark-and-sweep.
Một đối tượng được khởi tạo bằng new sẽ được lưu trữ trên heap và được thêm vào GC Map. Riêng các thread còn được hoạt động và static resource sẽ không nằm trong sự quản lí của GC. Tức là chúng sẽ không bao giờ bị GC thu hổi.
Hình 3: Thu hồi các đối tượng không được sử dụng bằng GC
Khi các object không dùng đến nữa, chúng không được thêm vào trong GC Map và GC sẽ toàn bộ các object không thuộc phạm vi của GC để thu hồi bộ nhớ.
Bonus 1: Cơ chế thu hồi bộ nhớ của GC
Tuy nhiên không phải cứ không nằm trong GC Map thì sẽ bị xóa đi khỏi bộ nhớ. Việc xóa dữ liệu, vẽ lại GC Map hay cụ thể là yêu cầu GC làm việc sẽ tốn rất nhiều toàn nguyên. Đôi khi việc này còn gây ra các hiện tượng giật, lag cho view. Chính vì vậy thông thường GC sẽ chờ đợi dung lượng của heap vượt qua một ngưỡng cho phép mới thực hiện thu hồi bộ nhớ.
Bonus: Thể hiện của memory leak.
Chúng ta có thể thấy rằng cơ chế thu hồi của GC khá linh hoạt. Chỉ trước khi vượt một ngưỡng nào đó hoặc CPU giảm xuông mức nhất định thì GC mới được tự động được gọi. Khi mà có quá nhiều những dữ liệu thừa sẽ dẫn tới việc hao phí bộ nhớ.
Bonus: Thể hiện của memory leak.
Khi bộ nhớ bị hao phí đến giới hạn và GC không thể giải phóng được dữ liệu cho đến khi gặp phải ngưỡng của heap size, một OutOfMemoryError sẽ được bắn ra và ứng dụng sẽ bị khởi động lại hoặc bị ép buộc phải kết thúc.
Hình 4: Tham chiếu ví dụ cho memory leak
Ví dụ phần tử ở vị trí A mặc dù đã không được sử dụng nữa nhưng vẫn nằm trong GC Map do nó đang được tham chiếu tới bởi phần tử B. Nếu hủy A đi thì B cũng sẽ nằm ngoài GC map do không có giá trị tham chiếu nào liên quan tới B nữa. Điều này dẫn tới tồn tại một vùng nhớ chứa A không được sử dung, tốn tài nguyên cho việc vẽ lại GC Map qua A.
Đây chính là Memory Leak – Sự rò rỉ bộ nhớ trong Java.
Ngày xưa đi học thầy mình bảo là trong Java không cần quan tâm tới vấn đề bộ nhớ. Nào ngờ bây giờ không chỉ phải quan tâm tới nó mà còn phải quan tâm tới thằng bạn nó là GC. Thậm trí còn không thể can thiệp được nó nữa chứ. Thật tệ hết sức tệ. Thế nhưng GC vẫn là một sản phẩm tuyệt vời khi mà chỉ cần nắm và hiểu một chút về nó, ta sẽ tiết kiệm rất nhiều công sức trong việc quản lí bộ nhớ.
Garbage collection != Immunity from memory leaks!
II.Những điều cần biết về dung lượng bộ nhớ
1.Làm thế nào để biết được chúng ta có bao nhiêu Heap được phép?
Có hai cách để biết được dung lượng bộ nhớ heap mà hệ điều hành cấp phát cho chúng ta là bao nhiêu.
ActivityManager.getmemoryClass();
Cách thứ nhất là sử dụng một phương thức đươc public trong Android là ActivityManager#getmemoryClass. Phương thức này sẽ trả ra thông tin về bộ nhớ chúng ta có được. Tuy nhiên phương thức này còn rất hên xui, tùy thuộc vào từng loại device và phiên bản của hệ điều hành mà chúng có được trả về hay không.
Runtime.getRuntime().maxMemory()
Cách tiếp theo chúng ta có thể tham khảo tới Class Runtime. Phương thức này sẽ trả về một số lớn hơn thực tế một chút. Thực tế mà tôi nói đến ở đây chính là sai khác so với thông tin trong file heap dump mà tôi sẽ trình bày sau trong chuỗi chủ đề này. Phương thức này còn cần ứng dụng của bạn phải được chạy ở quyền hệ thống. Hay chính xác hơn là bạn cần phải root máy nếu như chỉ viết một file .apk bình thường.
2.Tìm hiểu về thứ đang chiếm dụng heap – Object size.
Shallow size của một object – là tổng khối lượng bộ nhớ được sử dụng để lưu trữ object đó. Khối lượng bộ nhớ được tham chiếu sẽ không được tính đến. Nói một cách chính xác thì khi một object phảu đứng một mình và không có bất kì Object nào tham chiếu tới hay nó cũng không tham chiếu tới bất kì object nào thì tổng dung lượng nó chiếm được của heap chính là Shallow size. Chúng ta sẽ thử nhìn qua ví dụ sau:
public final class String { // 8 Bytes header
private char value[]; // 4 Bytes
private int offset; // 4 Bytes
private int count; // 4 Bytes
private int hash = 0; // 4 Bytes
…} // => "Shallow size" của một String == 24 Bytes (32 bit Sun JDK)
Retained set của một đối tượng X là tập hợp các đối tượng sẽ bị thu hồi bởi GC nếu đối tượng X bị xóa đi. Đồng thời retained size của một đối tượng X cũng chính bằng tổng số shallow size của retained sets của X. Có thể nói retained size chính là lượng không gian nhớ được giải phóng khi ta thu hồi một đối tượng.
III. Tổng kết
Các bạn đã vừa có một cái nhìn tổng quan về GC cũng như memory leak trong Java nói chung và Android nói riêng. Trong bài tiếp theo, chúng ta sẽ cùng tìm hiểu một số những vấn đề, nguyên nhân cũng như khắc phục và hạn chế được memory leak trong Android.
P/s: Trong bài trên của mình có một câu nói bị sai. Không ảnh hưởng nhiều đến kiến thức của các bạn nhưng đố bạn nào tìm được chỗ sai. Nếu tìm được, đó mới chứng tỏ việc các bạn hiểu được bài viết này.