JVM GC cho người chưa biết

Mục đích của bài viết.

Vào một ngày đẹp trời, một nhóm bạn trẻ vào hỏi tôi, một anh supporter, vào tư vấn giúp nhóm việc giảm size server cho đỡ tốn tiền. Tôi vào kiểm tra thấy có thể hạ được size server được, sau đó giải thích tại sao bằng việc đánh giá các thông số về JVM hiện tại. Các bạn trẻ gật gù tán thành. Sau 15 phút diễn giải, tôi hỏi các bạn trẻ hiểu chứ. Các bạn đồng thanh kêu chưa ạ. Đó là lý do tôi viết bài blog này. Các kiến thức trong bài viết là những kiến thức cơ bản, được tóm gọn mà các bạn có thể tìm thấy sau 1 phút tìm kiếm trên Internet. Rất nhiều bạn đã biết, nhưng cũng không ít bạn chưa biết. Hi vọng những người chưa biết có thể áp dụng các kiến thức trong bài viết vào công việc và ít cần anh supporter như tôi hơn.

JVM là gì?

Nhìn phức tạp thế thôi, bạn chỉ cần hiểu tên gọi đầy đủ là Java Virtual Machine, là máy ảo java. Dùng để chạy các chương trình java. Bài viết này sẽ tập trung vào Java 8. Phiên bản Java thông dụng nhất hiện tại, và bộ GC mặc định của Java 8 là ParallelGC

GC là gì?

Không giống như các ngôn ngữ kiểu C/C++, nơi bạn sẽ phải quản lý bộ nhớ (cấp phát, giải phóng) Máy ảo java thông minh hơn, sẽ giúp bạn bằng phân loại và dọn dẹp các đối tượng (object) trong bộ nhớ. Quá trình phân loại, dọn dẹp được gọi là Garbage Collection. Gọi tắt là GC

Phân loại: Chuyển các object còn được tham chiếu (live object) từ vùng nhớ này sang vùng nhớ khác.

Dọn dẹp: Với các object không còn được tham chiếu, các object này sẽ được xóa đi để dành khoảng trống để cấp phát cho các object mới.

 

Trên máy ảo Java cơ bản gồm 3 phân vùng bộ nhớ: “Young Generation” hay còn gọi là “New Generation”, “Old Generation” và “Permanent Generation” tương ứng với 3 sự kiện Young GC, Old GC và Permanent GC. Permanent GC với cấu hình mặc định sẽ không chạy và không được tính đến trong Heap Size (Non Heap) do đó chúng ta chỉ cần quan tâm đến 2 GC chính: Young GC và Old GC.

Việc phân loại cụ thể như sau: Khi được khởi tạo object sẽ được cấp phát bộ nhớ ở vùng nhớ Eden và sẽ tồn tại ở đó đến khi phân vùng Eden đầy, không thể cấp phát thêm, chương trình sẽ tiến hành Young GC. Sau khi sống sót qua lần Young GC đầu tiên, Object sẽ được chuyển vào vùng nhớ S0. Ở lần chạy GC kế tiếp, nếu Object vẫn còn được tham chiếu, sẽ chuyển sang vùng nhớ S1 cùng các Live Object mới từ Eden chuyển sang. Các object sẽ được chuyển qua lại giữa vùng S0 và S1 cho đến khi được đật được độ tuổi nhất định (tương ứng với số lần chuyển) sẽ được chuyển qua phân vùng Old Generation. Với phiên bản java 8 mặc định số tuổi là 15. Bạn có thể điều chỉnh số tuổi này bằng tham số MaxTenuringThreshold.

Để biết kích thước các vùng nhớ các bạn có thể tìm và điều chỉnh với các tham số: NewRation cho tỷ lệ giữa vùng nhớ Young Space và Old Space. SurvivorRation cho tỷ lệ giữa vùng nhớ Survivor Space và Eden.

Ví dụ: JVM được cấp phát bộ nhớ 10GB, cấu hình tương ứng: NewRatio=3, SurvivorRatio=4.

Lúc đó chúng ta có kích thước bộ nhớ tương ứng: Old Generation=7.5GB, Young Generation=2.5GB. Survivor Space S0 = 0.417 GB, Survivor Space S0 = 0.417 GB, Eden = 1.67GB.

Việc trigger Old GC, hay Full GC (Young + Old) GC sẽ phức tạp hơn Young GC, tùy thuộc vào từng bộ GC khác nhau. Chúng ta chưa cần quan tâm ở mức độ cơ bản.

Stop The World

GC thật tuyện vời khi thực hiện việc tối ưu bộ nhớ trên cho chúng ta, nhưng bữa ăn nào cũng có giá của nó. Mỗi khi JVM thực hiện GC, quá trình xử lý của jvm sẽ treo lại, hay còn được gọi với tên stop the world. Các tính tính toán sẽ dừng, request khách hàng gửi lên máy chủ cũng sẽ treo cho đến khi quá trình xử lý được thực hiện xong. Vậy nên hãy quan tâm đến việc lựa chọn và tối ưu GC nhiều hơn nếu bạn muốn chương trình của mình được nuột nà trong mắt khách hàng.

Quan sát trực quan các tham số trên công cụ theo dõi.

Các hình ảnh ở dưới được lấy từ một máy ảo chạy JVM trên môi trường “production” với công cụ monitor là Mackerel để các bạn dễ hình dung thực tế sẽ như thế nào.

Số lượng event xảy ra:

Thời gian cho các lần GC (theo đơn vị giây):

SpaceRate: Tỉ lệ % sử dụng của Young Generation và Old Generation so với dung lượng tối đa

Chi tiết về các vùng nhớ trong Young Generation:

 

Diễn giải:

  • NGCMX: Dung lượng tối đa của của phân vùng Young Generation có thể sử dụng
  • NGC: Dung lượng Young Generation đã commit. Có thể gọi là đã xí chỗ trên JVM
  • EU: Dung lượng vùng nhớ Eden sử dụng, đồ thị của phân vùng này thay đổi với biên độ lớn tương ứng với các lần chạy Young GC
  • S0U, S1U: Dung lượng sử dụng bởi S0 và S1

Một số tips hay ho với GC:

Làm thế nào để đo xem 1 tác vụ nào chạy hết bao nhiêu memory?

Do trong JVM phần lớn thời gian luôn tồn tại các Dead Object. Do đó đầu tiên hãy loại bỏ các object đó để việc tính toán trở nên chính xác bằng câu lệnh trigger Full GC: “jcmd <pid> GC.run” trong đó pid là process của java đang chạy. Đo thông số java đang sử dụng: bằng câu lệnh “jstat -gc <pid>” hoặc bạn có thể quan sát bằng công cụ monitor như Mackerel cho trực quan cho. Chạy tác vụ cần thiết và quan sát lại các thông số trong và sau khi chạy

Chạy GC làm treo chương trình nhiều, lâu quá, ảnh hưởng đến chất lượng dịch vụ:

Ngoài GC mặc định là ParallelGC, bạn có thể cân nhắc chuyển sang UseG1GC được thiết kế chỉ treo (Stop the world) khi chạy Full GC

Tham khảo:

Tags:,

Add a Comment

Scroll Up