Họ đang đo gì
Bạn có đọc log GC bao giờ chưa. Đây là nền cho mọi câu hỏi về OutOfMemoryError và pause time.
Trả lời ngắn~30 giây
Heap chứa object, chia thành young generation (eden + hai survivor) và old generation. Mỗi luồng có stack riêng cho khung gọi và biến nguyên thuỷ. Metaspace giữ metadata của lớp và nằm ở native memory từ Java 8 — thay cho PermGen. GC thế hệ dựa trên quan sát rằng phần lớn object chết rất trẻ: dọn eden thường xuyên và rẻ, thứ nào sống sót đủ nhiều lần thì thăng lên old gen, nơi dọn ít hơn nhưng đắt hơn.
Heap chia thành young generation (eden + hai vùng survivor) và old generation. Cách chia này dựa trên một quan sát: phần lớn object chết rất trẻ.
Giải thích sâu
Hai vùng survivor tồn tại để nén: minor GC sao chép object còn sống từ eden và survivor đang dùng sang survivor còn lại, rồi xoá sạch cả hai vùng nguồn cùng lúc. Vì thao tác là sao chép chứ không quét-và-xoá, chi phí tỉ lệ với lượng object CÒN SỐNG chứ không phải với lượng rác. Đó là lý do cấp phát nhiều object chết ngay gần như miễn phí trong JVM — điều nghe rất phản trực giác với người quen C++.
G1 (mặc định từ Java 9) đổi mô hình một chút: nó chia heap thành hàng nghìn vùng nhỏ đều nhau, mỗi vùng tại một thời điểm đóng vai eden, survivor hoặc old. Nhờ đó nó chọn dọn những vùng NHIỀU RÁC NHẤT trước — “garbage first” — và ước lượng được thời gian dừng theo mục tiêu bạn đặt (-XX:MaxGCPauseMillis). Với heap rất lớn thì ZGC hoặc Shenandoah phù hợp hơn, vì chúng dừng dưới mili giây bất kể heap bao lớn, đổi lại tốn thêm CPU và bộ nhớ.
Điều thực tế đáng nhớ khi debug: OutOfMemoryError: Java heap space là hết heap, thường do rò rỉ hoặc heap quá nhỏ; Metaspace là quá nhiều lớp được nạp, dấu hiệu kinh điển của việc nạp lại ứng dụng nhiều lần trong cùng JVM hoặc sinh lớp động; unable to create native thread thì không liên quan gì tới heap mà là hết bộ nhớ native hoặc chạm giới hạn tiến trình của hệ điều hành. Ba lỗi này cần ba hướng điều tra khác nhau.
Câu hỏi tiếp theo họ sẽ hỏi
?Stack overflow và heap overflow khác nhau ra sao?
StackOverflowError là chuỗi lời gọi quá sâu — thường do đệ quy không có điểm dừng — và giới hạn nằm ở -Xss cho từng luồng. OutOfMemoryError là không cấp phát được object mới trong heap. Cái đầu sửa bằng thuật toán, cái sau sửa bằng việc tìm cái gì đang giữ bộ nhớ.
?Chọn collector nào cho dịch vụ web độ trễ thấp?
G1 là mặc định hợp lý tới heap cỡ chục GB. Nếu p99 bị chi phối bởi pause thì ZGC đáng thử, vì pause của nó gần như không phụ thuộc kích thước heap. Nhưng trước khi đổi collector, hãy đo xem pause có thật sự là nguyên nhân — rất thường thì thủ phạm là chờ I/O chứ không phải GC.
Trả lời thế này là mất điểm
- Nhắc tới PermGen như thứ đang tồn tại. Nó bị bỏ từ Java 8 và thay bằng Metaspace.
- Nói “gọi
System.gc()để dọn bộ nhớ”. Đó chỉ là gợi ý, và trên hệ thống thật nó thường gây một lần dừng dài không cần thiết.