Garbage Collection


🔥 1. Tổng Quan về Garbage Collection trong Go

Go là ngôn ngữ có garbage collector (GC) built-in, được thiết kế để:

  • Tự động quản lý bộ nhớ (không cần free() như C/C++).

  • Tối ưu cho latency thấp, không pause quá lâu.

  • Concurrent, Generational và Non-compacting GC.

Từ Go 1.5 trở đi, GC của Go là Concurrent Mark-and-Sweep, được cải tiến qua từng version.


🧠 2. Cơ chế hoạt động của GC trong Go

Bước 1: Mark Phase (đánh dấu)

  • Tìm tất cả các object đang được tham chiếu từ root set (global vars, stack vars...).

  • Sử dụng tracing GC để theo dấu tất cả các object reachable.

  • Được thực hiện song song với các goroutines.

Bước 2: Sweep Phase

  • Dọn dẹp các object không còn được đánh dấu (unreachable).

  • Bộ nhớ được trả về heap và có thể tái sử dụng.

Điểm đặc biệt:

  • GC không compacting, nghĩa là không dời object trong heap (khác Java).

  • write barrier để hỗ trợ marking trong khi chương trình đang chạy.


⚙️ 3. GC Tuning & Metrics

Go cho phép bạn theo dõi và điều chỉnh GC để phù hợp với use-case:

⚙️ GC Parameters:

debug.SetGCPercent(x) // x là % tăng heap mới trước khi trigger GC
  • SetGCPercent(100) (default): Khi heap tăng 100% kể từ lần GC trước → trigger GC.

  • SetGCPercent(20): Dọn sớm hơn → latency thấp, nhưng CPU tốn hơn.

🔍 GC Metrics:

var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Println(memStats.NumGC)              // Số lần GC
fmt.Println(memStats.PauseNs)            // Thời gian pause
fmt.Println(memStats.Alloc, memStats.HeapAlloc) // Dung lượng đang cấp phát

🛠️ 4. GC Performance Tuning trong Production

Mục tiêu: Tối ưu giữa latencythroughput.

✅ Các chiến lược:

  • Giảm cấp phát không cần thiết (avoid unnecessary allocations).

    • Dùng sync.Pool cho object reuse.

    • Tránh tạo slice/map tạm thời trong hot path.

  • Giữ object nhỏ sống lâu trong stack thay vì heap:

    • GC trong Go không quét stack, nếu object chỉ nằm trong stack → không phải dọn.

  • Prefetch bộ nhớ (bằng warm-up request).

✅ sync.Pool – một kỹ thuật rất hay:

var bufPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 4096)
    },
}

// Lấy từ pool
buf := bufPool.Get().([]byte)
// Xử lý
...
// Trả về pool
bufPool.Put(buf)

Lợi ích: Giảm số lượng object GC phải xử lý → giảm áp lực GC.


🔥 5. High-level Concepts (Senior Level)

📌 Write Barrier

  • GC sử dụng "tri-color" marking: trắng (chưa mark), xám (chưa quét con), đen (quét xong).

  • Write barrier giúp tránh "lost updates" khi chương trình đang cập nhật object trong lúc GC đang chạy.

📌 GC Safe Points

  • Điểm mà goroutine có thể safely bị dừng lại để phục vụ GC.

  • Là lý do tại sao for {} mà không gọi hàm nào sẽ không bao giờ trigger GC (nó không có safe point).

📌 Stack Scanning

  • Go chỉ scan stack khi cần.

  • Object nằm hoàn toàn trong stack không bị GC dọn, nhưng khi escape ra heap thì phải track.


🧪 6. Thực nghiệm GC – Đo và Debug

Dùng GODEBUG để bật debug log:

GODEBUG=gctrace=1 ./myprogram

Kết quả:

gc 1 @0.030s 2%: 0.16+1.2+0.016 ms clock, 0.32+0.31/0.99/2.3+0.032 ms cpu, 4->4->2 MB, 5 MB goal, 8 P

Giải thích:

  • 0.16+1.2+0.016 ms: Mark start, concurrent mark, mark termination

  • 4->4->2 MB: Heap size before GC, after mark, after sweep

  • 5 MB goal: Target heap size to next GC

  • 8 P: Số logical CPU (P = processors)


🧠 7. Khi nào nên lo về GC?

Triệu chứng
Có thể là do GC

Latency spikes

Có thể là stop-the-world phase

CPU usage tăng

GC hoạt động quá thường xuyên

Memory tăng không giảm

Object bị giữ reference (memory leak)


✅ Tổng kết

Kỹ thuật Go GC
Ý nghĩa

SetGCPercent

Điều chỉnh khi nào GC chạy

sync.Pool

Giảm cấp phát heap

MemStats

Theo dõi GC trong runtime

GODEBUG=gctrace=1

Debug GC

Escape Analysis

Tối ưu để giữ object trong stack


Last updated