- 12 thủ thuật vô cùng hữu ích dành cho lập trình viên JavaScript
- Mảng và đối tượng trong JavaScript giống như cuốn truyện và tờ báo!
- Hướng dẫn cách chạy / mở file Java (.jar)
Trong Java, rác (garbage) có nghĩa là các đối tượng không còn được tham chiếu và bộ thu gom rác (garbage collection) được sử dụng để thực hiện quá trình tự động khôi phục lại bộ nhớ. Nói cách khác, đó là một cách để phá hủy các đối tượng không sử dụng nữa.
Vậy Garbage Collection trong Java có điểm gì đáng chú ý? Hãy cùng Quản Trị Mạng tìm hiểu về khái niệm của Garbage Collection, cách thức hoạt động và tại sao nó lại quan trọng trong bài viết này nhé!
1. Khái niệm về Garbage Collection
Garbage Collection (Bộ thu gom rác) trong Java được định nghĩa như một quá trình tự động thực thi nhiệm vụ quản lý bộ nhớ.
Code Java được dịch sang bytecode rồi chạy trên máy ảo Java hay viết tắt là JVM. Trong quá trình chạy chương trình, các đối tượng được tạo ở vùng nhớ heap, một phần bộ nhớ dành cho chương trình. Sau cùng, sẽ có một vài đối tượng mà chương trình không cần dùng đến. Các đối tượng này sẽ được garbage collector truy tìm và xóa bỏ để thu hồi lại dung lượng bộ nhớ.
Không gian trống này sẽ được cấp phát cho những đối tượng mới. Với các ngôn ngữ lập trình như C, việc giải phóng bộ nhớ được thực hiện một cách thủ công (bằng những lệnh khởi tạo, giải phóng bộ nhớ). Với Java, việc giải phóng bộ nhớ được thực hiện một cách tự động.
Xem thêm: Viết và chạy code Java trên máy tính lần đầu tiên
2. Garbage collection trong Java hoạt động như thế nào?
Garbage Collection trong Java là một quá trình hoàn toàn tự động. Các lập trình viên không cần gọi các lệnh "dọn" bộ nhớ như trong ngôn ngữ lập trình C/ C++. Phần hệ thống xử lý của garbage collection nằm trong JVM. Mỗi JVM lại có một cách cài đặt bộ dọn rác 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ấy ra làm ví dụ vì nó đang được sử dụng phổ biến nhất và bộ dọn rác của HotSpot có nhiều tính năng mạnh mẽ hơn so với những người anh em khác.
HotSpot có nhiều bộ dọn rác nhưng tựu chung lại các bộ dọn rác này đều hoạt động theo mô hình chung. Đầu tiên, xác định và đánh dấu các đối tượng không có tham chiếu (unreferenced object) sẵn sàng để thu gom rác. Bước thứ hai, xóa các đối tượng đã được đánh dấu. Tùy chọn, bộ nhớ có thể được "nén" lại sau khi bộ garbage collector tiến hành xóa các đối tượng, điều này đồng nghĩa với việc các đối tượng đ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 "nén" này giúp cấp phát bộ nhớ cho các đối tượng mới dễ dàng hơn sau khi khối bộ nhớ được phân bổ cho các đối tượng hiện có.
Tất cả các garbage collector của HotSpot thực hiện theo một chiến thuật chung là chia đối tượng theo "tuổi hoạt động". Lý do đằng sau việc chia bộ thu dọn rác theo tuổi hoạt động là vì hầu hết các vật thể đều tồn tại trong thời gian ngắn và sẽ sẵn sàng thu gom ngay sau khi tạo ra.
Theo biểu đồ bên trên, ta thấy tuổi hoạt động của các đối tượng được chia thành 3 nhóm tuổi: Young generation - Thế hệ trẻ, Old generation - Thế hệ già và Permanent generation - Thế hệ bất tử.
- Young generation - Thế hệ trẻ: Nhóm này được chia thành 2 nhóm con là Eden (Khởi thủy) và Survivor (Sống sót). Trong nhóm Survivor lại được chia thành 2 nhóm nhỏ hơn là S0 và S1. Ban đầu, các đối tượng mới được khởi tạo sẽ nằm trong nhóm Eden. Sau một chu kỳ hoạt động của garbage collector, đối tượng nào "sống sót" sẽ được chuyển sang nhóm Survivor. Sự kiện các đối tượng ở nhóm Young generation được thu hồi bởi Garbage collector được xem là Minor event. Sau "nhiều" chu kỳ quét mà đối tượng vẫn còn được sử dụng thì chúng mới được chuyển sang vùng nhớ Old generation.
- Old generation - Thế hệ già: Nhóm này chứa các đối tượng 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 đối tượng ở nhóm Old generation được thu hồi bởi garbage collector được xem là Major event.
- Permanent generation - Thế hệ 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.
Trong một sự kiện thu gom rác, các vật dụng không sử dụng trong tất cả các thế hệ là rác thải thu được. HotSpot chứa 4 bộ Garbage collector khác nhau:
- Serial: Các event thu gom rác được xử lý tuần tự trên một thread. Quá trình dồn bộ nhớ (compaction) được thực thi sau mỗi event thu gom rác. Serial là bộ garbage collector đơn giản nhất, đượ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.
- Parallel: Parallel 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 một 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. Tuy nhiên, khi hoạt động Parallel, nó sẽ dừng thread chạy chương trình.
- Concurrent Mark Sweep (CMS): 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ớ. CMS tiêu tốn nhiều tài nguyên CPU hơn nhưng 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.
- Garbage First (G1): Đây là bộ garbage collector mới nhất, ra đời cùng 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. G1 có khả năng vừa tiến hành thu hồi vừa dồn bộ nhớ, còn CMS chỉ có thể dồn bộ nhớ ở trong trạng thái STW. Hơn nữa, G1 có khả năng nhận diện các xâu ký tự trùng nhau trong heap (được tham chiếu bởi các đối tượng khác nhau) và sửa tham chiếu nhằm tránh các bản copy thừa của các xâu ký tự, tiết kiệm dung lượng trống cho vùng nhớ heap.
Xem thêm: 5 tính năng thú vị về Java 9 có thể bạn chưa biết
3. Lợi ích của garbage collection trong Java
Lập trình C/ C++ chắc hẳn đã từng gặp khó khăn 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, 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.
Xem thêm: 9 điều lập trình viên Java nên biết trong năm 2018 nếu muốn một sự nghiệp thành công
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 cao khả năng 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à việc thu gom rác ở Java không xác định được và không thể dự đoán garbage collector sẽ chạy ở thời điểm nào. Kể cả khi 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). Flag có thể điều chỉnh bộ thu gom rác (ví dụ: Serial, G1, v.v.), 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". Ví dụ: bộ garbage collector Parallel có hiệu quả nhưng sẽ gây ra sự cố "stop the world", làm cho nó phù hợp hơn cho việc xử lý phụ trợ, nơi được chấp nhận tạm dừng để thu gom rác.
Mặt khác, bộ garbage collector CMS được thiết kế để giảm thiểu thời gian tạm dừng, làm cho nó trở nên lý tưởng cho các ứng dụng GUI, nơi đáp ứng quan trọng. Điều chỉnh bổ sung có thể được thực hiện bằng cách thay đổi kích thước heap hoặc các phần của nó và đo hiệu quả thu gom rác bằng cách sử dụng một công cụ như jstat.
Tham khảo thêm một số bài viết:
- Top 5 CSS Framework phổ biến mà bạn cần lưu ý
- Hướng dẫn cách biên dịch và thực thi Java bằng Command Prompt
- Thống kê những ngôn ngữ lập trình bị "ghét" nhất
Chúc các bạn vui vẻ!