Hiểu sâu về cơ chế Garbage Collection và tối ưu hóa hiệu năng trong JVM

Phân vòng đời đối tượng và cấu trúc bộ nhớ Heap

JVM sử dụng cơ chế "Generational Collection" để quản lý bộ nhớ, nơi các đối tượng được phân loại dựa trên thời gian tồn tại của chúng và lưu trữ ở các vùng nhớ khác nhau:

  • Young Generation (Thế hệ trẻ): Khu vực lưu trữ các đối tượng có vòng đời ngắn ("sinh và chết nhanh"). Hầu hết các đối tượng mới được cấp phát ở đây. Thống kê cho thấy khoảng 80-90% đối tượng sẽ trở thành rác ngay lập tức trong vùng này.
  • Tenured Generation (Old Generation - Thế hệ già): Chứa các đối tượng đã sống sót qua nhiều chu trình thu gom rác trong thế hệ trẻ. Đây thường là các đối tượng toàn cục hoặc cache được sử dụng lâu dài.
  • Metaspace (Kế thừa PermGen): Lưu trữ metadata của lớp (class), phương thức và thông tin của hằng số. Kích thước của vùng này có thể mở rộng động tùy thuộc vào native memory.

Các loại Garbage Collection và điều kiện kích hoạt

1. Minor GC (Young GC)

Đây là quá trình thu gom rác diễn ra trên vùng Young Generation. Nó diễn ra thường xuyên và tốc độ xử lý rất nhanh.

Điều kiện kích hoạt: Khi vùng Eden trong Young Generation không còn đủ không gian để cấp phát cho đối tượng mới. Tại thời điểm này, các đối tượng còn sống tại Eden và một vùng Survivor (From) sẽ được di chuyển sang vùng Survivor còn lại (To). Luôn có một vùng Survivor trống sẵn để phục vụ quá trình này.

2. Full GC (Major GC)

Quá trình này thu gom rác trên toàn bộ vùng Heap (bao gồm cả Young và Old Generation). Full GC gây ra hiện tượng Stop-The-World (STW), làm tạm dừng toàn bộ ứng dụng, do đó nó ảnh hưởng lớn đến hiệu năng hệ thống.

Điều kiện kích hoạt:

  • Thiếu hụt vùng Old: Kích thước các đối tượng cần chuyển từ Young sang Old lớn hơn vùng nhớ trống của Old Generation.
  • Failure trong việc cấp phát: Khi không còn đủ không gian trong các vùng Survivor để chứa đối tượng khi thực hiện Young GC, buộc phải chuyển thẳng sang Old nhưng Old cũng đã đầy.
  • Gọi thủ công: Khi mã nguồn gọi lệnh System.gc(). JVM chỉ thực hiện "gợi ý" có thể chạy Full GC, không đảm bảo thực hiện ngay lập tức.

Quy trình cấp phát bộ nhớ và xử lý OOM

Khi một đối tượng mới được tạo ra, JVM thực hiện quy trình sau:

  1. Cố gắng cấp phát trong vùng Eden.
  2. Nếu Eden đầy, thực hiện Minor GC để dọn dẹp các đối tượng không còn sử dụng.
  3. Sau khi dọn dẹp, nếu vẫn không đủ chỗ, các đối tượng còn sống sẽ được chuyển xuống Survivor. Nếu vượt quá ngưỡng tuổi (tenuring threshold), chúng sẽ được thăng cấp (promote) lên Old Generation.
  4. Nếu Old Generation đầy, JVM thực hiện Full GC.
  5. Nếu sau Full GC vẫn không đủ bộ nhớ chứa đối tượng mới, JVM ném ngoại lệ java.lang.OutOfMemoryError: Java heap space.
  6. Lưu ý: Ngoài lỗi Heap space, còn có lỗi OutOfMemoryError: Metaspace khi nạp quá nhiều Class. Giải pháp là tăng thông số -XX:MaxMetaspaceSize.

    Các thuật toán và bộ thu gom rác (Garbage Collectors)

    JVM cung cấp nhiều bộ thu gom khác nhau, mỗi loại phù hợp với từng kịch bản cụ thể:

    1. Serial Collector

    Sử dụng duy nhất một luồng để thực hiện việc thu gom rác. Khi GC chạy, tất cả các luồng ứng dụng bị dừng lại (STW). Phù hợp với các ứng dụng đơn luồng và bộ nhớ nhỏ (Client mode).

    • Thuật toán: Copying (cho Young), Mark-Compact (cho Old).

    2. Parallel Collector (Throughput Collector)

    Sử dụng nhiều luồng (threads) song song để thực hiện thu gom rác. Mặc dù vẫn gây STW, nhưng nhờ chạy đa luồng nên thời gian dừng được rút ngắn đáng kể so với Serial. Đây là mặc định cho Server mode nếu không chỉ định G1.

    • Thuật toán: Copying (cho Young), Mark-Compact (cho Old).

    3. CMS Collector (Concurrent Mark Sweep)

    Mục tiêu là giảm thiểu thời gian dừng (low latency). Phần lớn quá trình thu gom chạy song song với luồng ứng dụng. Tuy nhiên, nó sử dụng thuật toán Mark-Sweep, gây raFragmentation (mảnh hóa bộ nhớ).

    • Thuật toán: Copying (Young bằng ParNew), Mark-Sweep (Old).

    4. G1 Garbage Collector (Garbage First)

    Là bộ thu gom thế hệ mới, mặc định từ JDK 9 trở đi. G1 chia Heap thành nhiều vùng nhỏ (Region) có kích thước bằng nhau. Nó ưu tiên dọn dẹp các vùng chứa nhiều rác nhất trước (Garbage First). G1 sử dụng thuật toán Mark-Compact để tránh mảnh hóa và cho phép dự đoán thời gian dừng.

    Cấu hình và tinh chỉnh tham số GC

    Các tham số JVM thường chia làm 3 loại: Standard (-), Non-standard (-X), và Unstable (-XX). Dưới đây là các thông số cấu hình quan trọng cần lưu ý.

    1. Chọn loại GC

    # Sử dụng Serial GC
    -XX:+UseSerialGC
    
    # Sử dụng Parallel GC (Ưu tiên throughput)
    -XX:+UseParallelGC
    
    # Sử dụng CMS (Ưu tiên thời gian phản hồi - Low Latency)
    -XX:+UseConcMarkSweepGC
    
    # Sử dụng G1 GC (Khuyên dùng cho Heap lớn > 4GB)
    -XX:+UseG1GC
    

    2. Tinh chỉnh bộ thu gom song song (Parallel GC)

    # Số luồng GC chạy song song
    -XX:ParallelGCThreads=8
    
    # Mục tiêu thời gian dừng tối đa cho Young GC (ms)
    -XX:MaxGCPauseMillis=200
    
    # Tỷ lệ thời gian chạy GC so với tổng thời gian chạy (1/(N+1))
    -XX:GCTimeRatio=99
    
    # Tự động điều chỉnh kích thước các vùng nhớ để đạt mục tiêu hiệu năng
    -XX:+UseAdaptiveSizePolicy
    

    3. Tinh chỉnh CMS Collector

    # Bật tính năng nén phân mảnh sau Full GC (tăng thời gian dừng nhưng giảm mảnh hóa)
    -XX:+UseCMSCompactAtFullCollection
    
    # Ngưỡng kích hoạt CMS khi Old Generation đã đầy bao nhiêu % (mặc định 92%)
    -XX:CMSInitiatingOccupancyFraction=75
    
    # Số lần Full GC chạy đến khi thực hiện nén vùng nhớ
    -XX:CMSFullGCsBeforeCompaction=0
    
    # Thêm luồng ParNew để hỗ trợ thu gom Young Generation khi dùng CMS
    -XX:+UseParNewGC
    

    4. Tinh chỉnh G1 GC

    # Mục tiêu thời gian dừng tối đa (ms) cho cả Young và Mixed GC
    -XX:MaxGCPauseMillis=200
    
    # Kích thước của một Region (phải là lũy thừa của 2, từ 1MB đến 32MB)
    -XX:G1HeapRegionSize=16m
    
    # Dự trữ phần trăm bộ nhớ để tránh thất bại trong việc sao chép (mặc định 10%)
    -XX:G1ReservePercent=15
    

    5. Các tham số nâng cao và xử lý lỗi

    Lỗi Promotion Failed trong CMS thường xảy ra khi Survivor quá nhỏ và Old Gen không đủ chỗ chứa đối tượng cần thăng cấp trong lúc Young GC chạy.

    Lỗi Concurrent Mode Failure xảy ra khi CMS đang chạy dọn rác nhưng tốc độ cấp phát bộ nhớ của ứng dụng nhanh hơn tốc độ dọn rác, khiến Old Gen bị đầy trước khi CMS kịp hoàn tất.

    Các tham số hỗ trợ khác:

    # Vô hiệu hóa việc gọi System.gc() từ code (tránh Full GC bất ngờ)
    -XX:+DisableExplicitGC
    
    # Độ tuổi tối đa đối tượng tồn tại ở Survivor trước khi lên Old Gen (mặc định 15)
    -XX:MaxTenuringThreshold=10
    
    # Kích thước ngưỡng đối tượng vượt qua Survivor để vào thẳng Old Gen (bytes)
    -XX:PretenureSizeThreshold=3145728
    

Thẻ: JVM garbage collection Java Performance tuning G1 GC

Đăng vào ngày 1 tháng 6 lúc 21:26