#1 Khái niệm

Trong Java, Garbage Collection được định nghĩa như một quá trình tự động thực thi nhiệm vụ quản lý bộ nhớ.

Chúng ta đều biết mã Java được dịch sang bytecode rồi chạy trên JVM, trong quá trình chạy chương trình, các object được tạo ở vùng nhớ heap. Sau cùng, sẽ có một vài object mà chương trình không cần đến. Các object này sẽ được garbage collector truy tìm và xóa bỏ để thu hồi lại dung lượng bộ nhớ.

#2 Garbage Collector của Java làm việc như thế nào?

Trong Java, bộ garbage collector làm việc hoàn toàn tự động. Đồng nghĩa với việc lập trình viên không cần gọi các lệnh "dọn" bộ nhớ như trong C/C++.

Phần Implementation của garbage collector nằm trong JVM. Mỗi JVM lại có một cách implement garbage collector khác nhau, phù hợp với những đặc tính của JVM đó. Trong số các đại diện JVM, Oracle HotSpot sẽ được chúng tôi lất ra làm ví dụ vì nó đang được sử dụng phổ biến nhất và bộ garbage collector của HotSpot có nhiều tính năng mạnh mẽ hơn những người anh em khác.

HotSpot có nhiều bộ garbage collector nhưng tựu chung lại thì các bộ garbage collector này đều hoạt động theo mô hình chung như sau:

  • Bước 1: xác định và đánh dấu các object không có tham chiếu (unreferenced object).
  • Bước 2: xóa các object được đánh dấu sau bước 1

Tùy chọn: bộ nhớ sau khi tiến hành bước 2 có thể được "dồn" lại, điều này đồng nghĩa với việc các object đang hoạt động sẽ nằm ở các ô nhớ sát nhau tại phần bắt đầu của heap. Quá trình "dồn" này giúp cấp phát bộ nhớ cho các object mới dễ dàng hơn.

Đó là mô hình hoạt động, còn về cách triển khai, tất cả garbage collector của HotSpot được implement theo một chiến thuật chung: chia object theo "tuổi hoạt động".

Theo biểu đồ, ta thấy tuổi hoạt động của object được chia thành 3 nhóm tuổi: Young  generation - thế hệ"trai trẻ", old generation - thế hệ "già", và permanent generation - thế hệ "bất tử".

  • Young generation - thế hệ "trẻ": nhóm này lại được chia thành 2 nhóm con là eden (khởi thủy) và survivor (sống sót). Nhóm survivor lại được chia thành 2 nhóm nhỏ hơn là S0 và S1. Các object mới được khởi tạo sẽ nằm trong nhóm Eden. Sau 1 chu kỳ hoạt động của garbage collector, object nào "sống sót" sẽ được chuyển sang nhóm survivor. Sự kiện các object ở nhóm Young generation được thu hồi bởi Garbage collector được xem là minor event.
  • Old generation - thế hệ "già" : nhóm này chứa các object chuyển từ young generation (tất nhiên với thời gian hoạt động đủ lâu, mỗi bộ garbage collector sẽ định nghĩa bao nhiêu được coi là "lâu"). Sự kiện các object ở nhóm Old generation được thu hồi bởi garbage collector được xem là major event.
  • Permanent generation - "bất tử": nhóm này gồm metadata (ví dụ: các class, method....). Do đó, khi phải "dọn" các class, method không cần thiết, garbage collector sẽ tìm kiếm trong nhóm này.

HotSpot chứa 4 bộ Garbage Collector khác nhau:

1.Serial:

Các event được xử lý tuần tự trên 1 thread. Quá trình dồn bộ nhớ (compaction) được thực thi sau mỗi event.

Serial là bộ garbage collector đơn giản nhất. Nó được thiết kế cho môi trường single thread. Khi Serial hoạt động, ứng dụng buộc phải dừng lại. Do đó nó không phù hợp với môi trường server.

Để sử dụng thử Serial, bạn có thể thêm lệnh -XX:+UseSerialGC khi chạy chương trình.

2.Parallel:

Đây là garbage collector mặc định của Java.

Minor event sẽ được xử lý trên nhiều thread. Major event và quá trình dồn bộ nhớ cho nhóm Old generation được xử lý trên 1 thread. Bên cạnh bộ Parallel, có một bộ khác tên là Parallel Old xử lý major event và quá trình dồn bộ nhớ (cho nhóm Old generation) trên nhiều thread.

Nhược điểm của Parallel là khi hoạt động, nó sẽ dừng thread chạy chương trình.

3. CMS

Concurrent Mark Sweep: CMS là sự kết hợp và cải tiến của Parallel và Parallel Old. Nó xử lý minor event trên nhiều thread (giống với Parallel) và xử lý major event trên nhiều thread (giống với Parallel Old). Bên cạnh đó, CMS chạy song song với ứng dụng và đảm bảo quá trình dọn rác không làm ảnh hướng tới quá trình thực thi ứng dụng. CMS không tiến hành dồn bộ nhớ.

So với 2 bộ garbage collector kể trên thì CMS tiêu tốn nhiều tài nguyên CPU hơn. Tuy nhiên nó lại không làm ảnh hưởng tới quá trình thực thi ứng dụng (hay còn gọi là trạng thái Stop The World - STW). Đối với các server hoặc các ứng dụng gặp bất lợi khi phải STW thì sử dụng CMS là lựa chọn phù hợp. 

CMS mặc định không hoạt động, ta cần  bật nó lên bằng cách sử dụng tham số: XX:+USeParNewGC.

4. G1

Hay còn gọi là Garbage first. Đây là bộ garbage collector mới nhất, ra đời cùng với Java 7 với mục tiêu thay thế cho CMS trong việc quản lý các vùng heap >4GB. G1 sử dụng nhiều background thread để scan qua vùng heap (được chia thành các vùng có dung lượng từ 1 đến 32MB). G1 sẽ thu dọn ở các vùng nhớ có nhiều "rác" nhất. So với CMS, G1 có khả năng vừa tiến hành thu hồi vừa dồn bộ nhớ (CMS chỉ có thể dồn bộ nhớ ở trong trạng thái STW).

Mặc định, G1 không được bật, do đó ta cần sử dụng tham số: –XX:+UseG1GC.

String deduplication - tính năng tối ưu hóa thú vị của G1t.

Nhắc đến G1, ta không thể bỏ qua tính năng này. G1 có khả năng nhận diện các xâu ký tự trùng nhau trong heap (nhưng lại được tham chiếu bởi các object khác nhau) và sửa tham chiếu đến chúng, nhằm tránh các bản copy thừa của các xâu ký tự này, tiết kiệm dung lượng trống cho vùng nhớ heap.

Để sử dụng tính năng String deduplication, ta truyền tham số: -XX:+UseStringDeduplication

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

#3 Lợi ích

Ai lập trình C/C++ hẳn đã từng khổ sở khi đương đầu với Memory Leaks. Tuy nhiên với Java nói riêng và các ngôn ngữ sở hữu Garbage Collector nói chung, cực hình này đã được chấm dứt hoàn toàn.

Trong khi giới lập trình vẫn đang tranh cãi về khả năng hoạt động hiệu quả của Garbage Collector và phe bảo thủ một mực khẳng định rằng việc quản lý bộ nhớ bằng tay sẽ mang lại khả năng kiểm soát và hiệu năng tốt hơn thì Garbage Collector đang trở thành tính năng "chuẩn" của nhiều ngôn ngữ lập trình mới.

Đối với Java, khi quá trình hoạt động của  garbage collector có nguy cơ ảnh hưởng tới performance của ứng dụng, lập trình viên có thể tùy chỉnh garbage collector theo nhiều cách khác nhau để đạt được hiệu năng mong muốn.

#4 Làm việc với garbage collector sao cho hiệu quả

Với các ứng dụng đơn giản, lập trình viên không cần quan tâm nhiều đến garbage collector. Tuy nhiên nếu họ muốn nâng level của bản thân thì việc hiểu cơ chế hoạt động và tùy chỉnh bộ garbage collector là một trong các kỹ năng phải học.

Bên cạnh cơ chế hoạt động của garbage collector, các lập trình viên cần lưu ý một điều, đó là không thể dự đoán garbage collector sẽ chạy ở thời điểm nào. Kể cả khi ta có gọi nó một cách tường minh với System.gc() hay Runtime.gc(), ta vẫn không thể chắc chắn được garbage collector có chạy hay không.

Về việc tùy chỉnh garbage collector, cách tiếp cận tốt nhất là điều chỉnh các setting flag của JVM (chính là các tham số tương ứng với từng bộ garbage collector được liệt kể ở trên), hoặc quy định kích thước khi cấp phát và kích thước tối đa của vùng nhớ heap mà chương trình sử dụng, hoặc điều chỉnh kích thước của từng nhóm "tuổi".

Techmaster via Dzone