Máy ảo Java - GC thế hệ
Hầu hết các JVM chia heap thành ba thế hệ - the young generation (YG), the old generation (OG) and permanent generation (also called tenured generation). Những lý do đằng sau suy nghĩ như vậy là gì?
Các nghiên cứu thực nghiệm đã chỉ ra rằng hầu hết các đối tượng được tạo ra đều có tuổi thọ rất ngắn -
Nguồn
https://www.oracle.com
Như bạn có thể thấy rằng khi ngày càng có nhiều đối tượng được phân bổ theo thời gian, thì số lượng byte còn sót lại trở nên ít hơn (nói chung). Các đối tượng Java có tỷ lệ tử vong cao.
Chúng ta sẽ xem xét một ví dụ đơn giản. Lớp String trong Java là bất biến. Điều này có nghĩa là mỗi khi bạn cần thay đổi nội dung của một đối tượng String, bạn phải tạo một đối tượng mới hoàn toàn. Giả sử bạn thực hiện thay đổi chuỗi 1000 lần trong một vòng lặp như được hiển thị trong đoạn mã dưới đây -
String str = “G11 GC”;
for(int i = 0 ; i < 1000; i++) {
str = str + String.valueOf(i);
}
Trong mỗi vòng lặp, chúng tôi tạo một đối tượng chuỗi mới và chuỗi được tạo trong lần lặp trước đó sẽ trở nên vô dụng (nghĩa là nó không được tham chiếu bởi bất kỳ tham chiếu nào). Thời gian tồn tại của đối tượng đó chỉ là một lần lặp lại - chúng sẽ được GC thu thập ngay lập tức. Những đồ vật tồn tại trong thời gian ngắn như vậy được lưu giữ trong khu vực thế hệ trẻ của đống. Quá trình thu thập các đồ vật từ thế hệ trẻ được gọi là thu gom rác nhỏ, và nó luôn gây ra sự tạm dừng 'thế giới dừng lại'.
Khi thế hệ trẻ được lấp đầy, GC thực hiện một công việc thu gom rác nhỏ. Các vật thể chết được loại bỏ, và các vật thể sống được chuyển sang thế hệ cũ. Các chuỗi ứng dụng dừng trong quá trình này.
Ở đây, chúng ta có thể thấy những lợi thế mà một thiết kế thế hệ như vậy mang lại. Thế hệ trẻ chỉ là một phần nhỏ trong đống và bị lấp đầy nhanh chóng. Nhưng quá trình xử lý mất ít thời gian hơn nhiều so với thời gian cần thiết để xử lý toàn bộ đống. Vì vậy, thời gian tạm dừng 'stop-theworld' trong trường hợp này ngắn hơn nhiều, mặc dù thường xuyên hơn. Chúng ta nên luôn hướng tới những lần tạm dừng ngắn hơn những lần tạm dừng dài hơn, mặc dù chúng có thể thường xuyên hơn. Chúng ta sẽ thảo luận chi tiết về vấn đề này trong các phần sau của hướng dẫn này.
Thế hệ trẻ được chia thành hai không gian - eden and survivor space. Các vật thể sống sót trong quá trình thu thập eden được chuyển đến không gian dành cho người sống sót, và những người sống sót trong không gian sống sót được chuyển đến thế hệ cũ. Thế hệ trẻ được nén chặt trong khi nó được thu thập.
Khi các đối tượng được chuyển sang thế hệ cũ, cuối cùng nó sẽ đầy lên và phải được thu thập và nén chặt. Các thuật toán khác nhau có những cách tiếp cận khác nhau đối với điều này. Một số người trong số họ dừng các luồng ứng dụng (điều này dẫn đến việc tạm dừng 'stop-the-world' trong một thời gian dài vì thế hệ cũ khá lớn so với thế hệ trẻ), trong khi một số người trong số họ thực hiện đồng thời trong khi các luồng ứng dụng tiếp tục chạy. Quá trình này được gọi là GC đầy đủ. Hai nhà sưu tập như vậy làCMS and G1.
Bây giờ chúng ta hãy phân tích chi tiết các thuật toán này.
GC nối tiếp
nó là GC mặc định trên các máy cấp khách (máy xử lý đơn hoặc 32b JVM, Windows). Thông thường, GC có nhiều luồng đa luồng, nhưng GC nối tiếp thì không. Nó có một luồng duy nhất để xử lý đống và nó sẽ dừng các luồng ứng dụng bất cứ khi nào nó đang thực hiện một GC nhỏ hoặc một GC chính. Chúng ta có thể ra lệnh cho JVM sử dụng GC này bằng cách chỉ định cờ:-XX:+UseSerialGC. Nếu chúng ta muốn nó sử dụng một số thuật toán khác, hãy chỉ định tên thuật toán. Lưu ý rằng thế hệ cũ được nén hoàn toàn trong một GC lớn.
Thông lượng GC
GC này được mặc định trên các máy JVM 64b và nhiều CPU. Không giống như GC nối tiếp, nó sử dụng nhiều luồng để xử lý thế hệ trẻ và thế hệ cũ. Do đó, GC còn được gọi làparallel collector. Chúng tôi có thể ra lệnh cho JVM của mình sử dụng bộ thu này bằng cách sử dụng cờ:-XX:+UseParallelOldGC hoặc là -XX:+UseParallelGC(dành cho JDK 8 trở đi). Các luồng ứng dụng bị dừng trong khi nó thực hiện một bộ sưu tập rác lớn hoặc nhỏ. Giống như bộ sưu tập nối tiếp, nó hoàn toàn thu gọn thế hệ trẻ trong thời kỳ GC lớn.
Thông lượng GC thu thập YG và OG. Khi eden đã đầy, bộ thu sẽ đẩy các vật thể sống từ nó vào OG hoặc một trong các không gian sống sót (SS0 và SS1 trong sơ đồ bên dưới). Các vật thể chết được loại bỏ để giải phóng không gian mà chúng chiếm dụng.
Trước GC của YG
Sau GC của YG
Trong một GC đầy đủ, bộ thu thập thông lượng làm trống toàn bộ YG, SS0 và SS1. Sau khi hoạt động, OG chỉ chứa các đối tượng sống. Chúng ta nên lưu ý rằng cả hai bộ sưu tập trên đều dừng các luồng ứng dụng trong khi xử lý đống. Điều này có nghĩa là 'stopthe- world' tạm dừng trong thời gian dài của GC lớn. Hai thuật toán tiếp theo nhằm mục đích loại bỏ chúng, với chi phí tốn nhiều tài nguyên phần cứng hơn -
Bộ sưu tập CMS
Nó là viết tắt của 'quét đánh dấu đồng thời'. Chức năng của nó là nó sử dụng một số luồng nền để quét qua thế hệ cũ theo định kỳ và loại bỏ các đối tượng đã chết. Nhưng trong một GC nhỏ, các luồng ứng dụng bị dừng. Tuy nhiên, số lần tạm dừng là khá nhỏ. Điều này làm cho CMS trở thành một bộ thu có khoảng dừng thấp.
Bộ sưu tập này cần thêm thời gian CPU để quét qua heap trong khi chạy các luồng ứng dụng. Hơn nữa, các luồng nền chỉ thu thập đống và không thực hiện bất kỳ quá trình nén nào. Chúng có thể dẫn đến đống trở nên phân mảnh. Khi điều này tiếp tục diễn ra, sau một thời gian nhất định, CMS sẽ dừng tất cả các luồng ứng dụng và thu gọn đống bằng một luồng duy nhất. Sử dụng các đối số JVM sau để yêu cầu JVM sử dụng bộ thu CMS:
“XX:+UseConcMarkSweepGC -XX:+UseParNewGC” như các đối số JVM để yêu cầu nó sử dụng bộ thu CMS.
Trước GC
Sau GC
Lưu ý rằng việc thu thập đang được thực hiện đồng thời.
G1 GC
Thuật toán này hoạt động bằng cách chia đống thành một số vùng. Giống như bộ sưu tập CMS, nó dừng các luồng ứng dụng trong khi thực hiện một GC nhỏ và sử dụng các luồng nền để xử lý thế hệ cũ trong khi vẫn giữ cho các luồng ứng dụng tiếp tục. Vì nó phân chia thế hệ cũ thành các vùng, nó tiếp tục nén chúng lại trong khi di chuyển các đối tượng từ vùng này sang vùng khác. Do đó, sự phân mảnh là tối thiểu. Bạn có thể sử dụng cờ:XX:+UseG1GCđể yêu cầu JVM của bạn sử dụng thuật toán này. Giống như CMS, nó cũng cần thêm thời gian CPU để xử lý đống và chạy các luồng ứng dụng đồng thời.
Thuật toán này đã được thiết kế để xử lý các đống lớn hơn (> 4G), được chia thành một số vùng khác nhau. Một số khu vực đó bao gồm thế hệ trẻ, và phần còn lại bao gồm những người già. YG được xóa bằng cách sử dụng truyền thống - tất cả các luồng ứng dụng bị dừng và tất cả các đối tượng vẫn còn sống đến thế hệ cũ hoặc không gian sống sót.
Lưu ý rằng tất cả các thuật toán GC đã chia đống thành YG và OG, đồng thời sử dụng STWP để xóa YG. Quá trình này thường rất nhanh.