---
title: "Memory Leak trong Go: cách Tìm và Khắc phục"
description: "Mặc dù Go có cơ chế thu gom rác (Garbage Collection), rò rỉ bộ nhớ vẫn có thể xảy ra nếu bộ nhớ không sử dụng vẫn bị tham chiếu một cách vô tình"
canonical: "https://200lab.io/blog/memory-leak-trong-go-cach-tim-va-khac-phuc"
published: "2025-04-04T03:59:58Z"
updated: "2026-08-21T07:18:00Z"
authors: ["Hướng nội"]
tags: ["Backend"]
reading_minutes: 4
access: "public"
---

Rò rỉ bộ nhớ có thể dẫn đến tiêu thụ bộ nhớ quá mức, giảm hiệu suất và thậm chí gây crash trong các ứng dụng Go chạy thời gian dài. Mặc dù Go có tính năng thu gom rác (Garbage Collection - GC), việc quản lý bộ nhớ không đúng cách vẫn có thể gây ra rò rỉ bộ nhớ. Bài viết này sẽ tìm hiểu cách xác định, debug và khắc phục memory leak trong các ứng dụng Go.

## 1. Hiểu về rò rỉ bộ nhớ trong Go

### 1.1 Nguyên nhân gây rò rỉ bộ nhớ

Mặc dù Go có cơ chế thu gom rác (Garbage Collection), rò rỉ bộ nhớ vẫn có thể xảy ra nếu bộ nhớ không sử dụng vẫn bị tham chiếu một cách vô tình. Dưới đây là một số nguyên nhân phổ biến gây ra tình trạng này:

- **Unbounded Goroutines**: Các goroutine không được kết thúc đúng cách hoặc tích lũy vô hạn định.
- **Large Persistent Slices/Maps**: Các Object được giữ trong bộ nhớ ngay cả khi không còn cần thiết.
- **Global Variables**: Giữ tham chiếu đến các đối tượng lớn, ngăn cản GC thu hồi bộ nhớ.
- **Bộ nhớ đệm không hợp lý**: Lưu trữ quá nhiều dữ liệu trong bộ nhớ mà không có chiến lược loại bỏ.

### 1.2 Dấu hiệu của rò rỉ bộ nhớ

- Mức độ sử dụng bộ nhớ tăng dần theo thời gian mà không được giải phóng.
- Lỗi hết bộ nhớ (OOM - Out of Memory).
- Hiệu suất ứng dụng suy giảm sau thời gian dài hoạt động.

## 2. Phát hiện rò rỉ bộ nhớ trong Go

### 2.1 Sử dụng `pprof` để phân tích

Package `pprof` giúp phân tích việc sử dụng bộ nhớ trong các ứng dụng Go. Cách thêm `pprof` vào ứng dụng của bạn:

```
import (
    _ "net/http/pprof"
    "net/http"
    "log"
)

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    // Logic ứng dụng của bạn ở đây
}
```

Đoạn code này khởi động một máy chủ HTTP trên cổng `6060`, cung cấp dữ liệu phân tích tại `http://localhost:6060/debug/pprof/`.

Phân tích việc sử dụng Heap, chạy lệnh sau để thu thập thông tin về heap:

```
go tool pprof http://localhost:6060/debug/pprof/heap
```

Trong terminal, sử dụng lệnh `top` hoặc `list` để phân tích việc sử dụng bộ nhớ.

### 2.2 Sử dụng `runtime.ReadMemStats`

Package `runtime` cung cấp thống kê bộ nhớ theo thời gian thực:

```
import (
    "fmt"
    "runtime"
)

func printMemUsage() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("Alloc = %v KB\n", m.Alloc/1024)
    fmt.Printf("TotalAlloc = %v KB\n", m.TotalAlloc/1024)
}
```

## 3. Các rò rỉ bộ nhớ phổ biến và cách khắc phục

### 3.1 Unbounded Goroutines

- Vấn đề: Goroutines được tạo ra trong vòng lặp mà không có điều kiện kết thúc.

```
for i := 0; i < 1000; i++ {
    go func() {
        for {
            // Vòng lặp vô hạn trong goroutine
        }
    }()
}
```

- Khắc phục: Sử dụng `context.Context` để kiểm soát vòng đời của goroutine.

```
import (
    "context"
    "time"
)

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

go func() {
    <-ctx.Done()
    return
}()
```

### 3.2 Large Persistent Data Structures

- Vấn đề: Slices hoặc maps lớn bị vô tình giữ lại.

```
var data = make([]byte, 100*1024*1024) // slice 100MB
```

- Khắc phục: Reset slices/maps khi không còn cần thiết.

```
data = nil // Cho phép thu gom rác
```

### 3.3 Unused References in Structs

- Vấn đề: Giữ tham chiếu đến các đối tượng lớn.

```
type User struct {
    profileData []byte
}
```

- Khắc phục: Đặt các trường thành `nil` sau khi sử dụng.

```
user.profileData = nil
```

## 4. Kết luận

Mặc dù Go có bộ thu gom rác để quản lý bộ nhớ, nhưng nếu sử dụng không đúng cách, rò rỉ bộ nhớ vẫn có thể xảy ra. Để ngăn chặn và khắc phục tình trạng này, bạn có thể:

- Theo dõi việc sử dụng bộ nhớ bằng công cụ **pprof**.
- Kiểm soát **goroutines** bằng cách sử dụng **context** một cách hợp lý.
- Tối ưu hóa các **cấu trúc dữ liệu** để tránh lãng phí bộ nhớ.

## Đọc thêm

- [Architecture Pattern - Phần 7: Hexagonal Architecture](https://200lab.io/blog/hexagonal-architecture-la-gi.md): Hexagonal Architecture là pattern kiến trúc mà mọi tương tác từ bên ngoài đều phải đi qua một Adapter để kết nối vào một Port của Application
- [File __init__.py trong Python: Hướng dẫn sử dụng toàn diện](https://200lab.io/blog/huong-dan-su-dung-toan-dien-file-init-trong-python.md): __init__.py không chỉ đơn thuần là một file đánh dấu package, nó quyết định cách package của bạn được cấu trúc, import và tối ưu ra sao
- [Thiết kế Leaderboard hiệu năng cao: Khi nào chọn SQL, Khi nào dùng Redis?](https://200lab.io/blog/thiet-ke-leaderboard-hieu-nang-cao-khi-nao-chon-sql-khi-nao-dung-redis.md): Khám phá cách thiết kế hệ thống Leaderboard tối ưu, linh hoạt, dễ mở rộng, kết hợp giữa tư duy thiết kế hệ thống hiện đại và kinh nghiệm thực tế
- [Hành vi giúp Phân biệt các Senior Engineer với những người còn lại](https://200lab.io/blog/hanh-vi-giup-phan-biet-cac-senior-engineer-voi-nhung-nguoi-con-lai.md): Việc trở thành một senior engineer thực sự không chỉ nằm ở chức danh bạn được giao, mà là kết quả của tư duy, cách tiếp cận công việc mà bạn rèn luyện mỗi ngày
- [Asynchronous Request-Reply Pattern: Giải pháp cho những tác vụ nặng](https://200lab.io/blog/asynchronous-request-reply-pattern-la-gi.md): Pattern giúp tách rời phần xử lý backend, diễn ra bất đồng bộ khỏi frontend nhưng vẫn phải đảm bảo frontend nhận được thông báo kết quả cuối cùng
- [Mọi bài viết chủ đề Backend](https://200lab.io/blog/tags/backend.md)

Toàn bộ nội dung: https://200lab.io/llms.txt
