Part 01 · Java Core · Chuyên đề 09

Garbage Collection & Production Diagnostics

Mục tiêu không phải thuộc flag JVM, mà hiểu memory pressure, collector trade-off và quy trình thu thập bằng chứng trước khi sửa.

1. Memory của JVM process

VùngNội dung
Java heapObject/array do application tạo, được GC quản lý.
Thread stackFrame, local variable, operand stack; mỗi thread có stack riêng.
MetaspaceClass metadata ở native memory; class loader leak làm metaspace tăng.
Code cacheMachine code do JIT compile.
Direct/native memoryDirectByteBuffer, JNI, GC structures, JVM internal allocation.

-Xmx chỉ giới hạn heap, không giới hạn toàn process. Container memory limit phải chừa headroom cho non-heap/native. Nhiều platform thread làm thread stack đáng kể; virtual thread stack được quản lý khác và co giãn.

2. Reachability và reference

GC bắt đầu từ roots như active thread stack, static field, JNI reference rồi đánh dấu object reachable. Strong reference giữ object sống. Soft/weak/phantom reference có semantics khác và không nên dùng như cache policy tùy tiện. Cleaner/phantom phù hợp fallback cleanup native resource, nhưng deterministic resource vẫn cần close.

Finalization là legacy path. Finalization bị deprecated for removal vì latency, resurrection và security problems. Không viết design mới dựa vào finalize().

3. Các khái niệm collector

Collector có thể dùng mark, sweep, compact, copying và region. Generational hypothesis nói phần lớn object chết trẻ, nên nhiều collector tối ưu young collection. Stop-the-world nghĩa application thread dừng ở safepoint; concurrent phase vẫn có thể dùng CPU/bandwidth cùng ứng dụng.

Collector Java 21Định hướng
Serial GCĐơn giản, một GC thread; heap nhỏ hoặc environment hạn chế.
Parallel GCƯu tiên throughput, dùng nhiều GC thread, pause thường dài hơn.
G1 GCMặc định phổ biến; region-based, cân bằng throughput và pause target.
ZGCLow-latency, phần lớn công việc concurrent; Java 21 có generational ZGC là lựa chọn cần bật.
ShenandoahLow-pause collector trong các OpenJDK distribution hỗ trợ; không phải mọi distribution giống nhau.

4. G1 mental model

Heap chia region. Young collection thu Eden/Survivor; concurrent marking tìm live object toàn heap; mixed collection thu young cùng một số old regions có nhiều garbage. Humongous object chiếm một hoặc nhiều region riêng và có thể gây pressure/fragmentation. Pause goal là mục tiêu heuristic, không phải SLA bảo đảm.

Tuning order: Đừng chỉnh hàng chục flag trước khi xem allocation rate, live set, pause distribution, CPU và heap headroom. Thường ưu tiên heap sizing hợp lý, giảm allocation/retention và cập nhật JDK trước.

5. Đọc GC log

java -Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags ...

Cần quan sát: heap before/after, pause time, cause, frequency, allocation rate, promotion, concurrent cycle và failure. GC chạy thường xuyên nhưng reclaim tốt có thể do heap nhỏ hoặc allocation cao; old occupancy chỉ tăng và không giảm gợi ý retention/leak; long pause có thể do live set lớn, humongous allocation, reference processing hoặc safepoint khác GC.

6. Công cụ thu thập bằng chứng

jcmd <pid> VM.version
jcmd <pid> VM.flags
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram
jcmd <pid> Thread.print -l
jcmd <pid> VM.native_memory summary
jcmd <pid> JFR.start name=incident settings=profile duration=120s filename=incident.jfr

JFR ghi event CPU, allocation, lock, file/socket I/O, GC và thread với overhead thấp phù hợp production. Thread dump là snapshot; lấy chuỗi snapshot. Class histogram cho count/bytes theo class nhưng không cho biết ai giữ object; heap dump + dominator/retained size mới giúp tìm retention path. Heap dump có thể lớn, gây pause và chứa dữ liệu nhạy cảm.

Native Memory Tracking cần bật từ startup và có overhead; dùng baseline/diff để thấy category tăng. OS tools vẫn cần để map process/thread CPU, RSS, page fault và file descriptor.

7. Quy trình theo triệu chứng

CPU cao

  1. Xác nhận process và thời gian xảy ra.
  2. Map native thread ID CPU cao sang Java thread dump.
  3. Lấy nhiều dump/JFR để phân biệt loop, contention, GC hay workload hợp lệ.
  4. Đo trước khi tối ưu.

OutOfMemoryError

Thông báoHướng điều tra
Java heap spaceHeap sizing, allocation burst, retention path, unbounded data.
GC overhead limit exceededGần hết heap, GC tốn phần lớn thời gian nhưng reclaim rất ít.
MetaspaceSố class/loader, dynamic generation, redeploy leak.
Unable to create native threadThread count, OS limit, native memory; không nhất thiết heap.
Direct buffer memoryDirect buffer allocation/lifetime/limit.

Latency cao

Đặt latency trên timeline cùng GC pause, safepoint, CPU, lock, pool/queue và downstream. Correlation không tự là causation. P99 tăng trong khi average ổn thường cần phân tích tail và saturation.

8. JIT và warmup

Hot methods được tiered compilation tối ưu dần; deoptimization có thể xảy ra khi assumption không còn đúng. Benchmark ngắn dễ đo interpreter/compilation thay vì steady state. Dùng JMH để có warmup, fork, dead-code prevention và thống kê phù hợp. Production startup, warmup và peak throughput là ba mục tiêu khác nhau.

9. Nguyên tắc tuning

Nguồn chính thức: Java 21 GC Tuning Guide · Troubleshooting Guide · JFR API · JEP 439 · Generational ZGC · JEP 421 · Deprecate Finalization