> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 如何写出内存泄露的程序？
- URL: https://huizhou92.com/ru-he-xie-chu-nei-cun-xie-lu-de-cheng-xu/
- Published: 2024-09-04T06:39:36.000Z
- Updated: 2026-09-08T02:30:30.000Z
- Description: 如何在 Go 中避免内存泄露？关注关闭文件、HTTP 响应体、字符串管理及 goroutine 使用。及时释放资源，避免循环引用，使用 defer 时要小心，助你写出健壮的代码！#GoLang #内存泄露
- Author: huizhou92
- Tags: tech, go, #lang-zh-cn

不管使用什么语言，内存泄露是经常遇到的一类问题，然而使用Go语言编写内存泄露的代码却不容易，本文将列举几个可能出现内存泄露的场景，从反例中学习如何避免内存泄露。

## 资源泄露

### 不关闭打开的文件

当你不再需要一个打开的文件,正常你需要调用它的Close方法,如果你不调用Close，就有可能文件描述符达到最大限制，无法在打开新的文件或者连接,程序会报`too many open files`的错误。比如下面的例子：  
Code 1: 文件没关闭导致 耗尽文件描述符。

```go
func main() {  
    files := make([]*os.File, 0)  
    for i := 0; ; i++ {  
       file, err := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)  
       if err != nil {  
          fmt.Printf("Error at file %d: %v\n", i, err)  
          break  
       } else {  
          _, _ = file.Write([]byte("Hello, World!"))  
          files = append(files, file)  
       }  
    }  
}
----
➜  memory_leak git:(main) ✗ go run close_file.go 
Error at file 61437: open test.log: too many open files

```

在我的Mac 电脑上一个进程能打开的文件句柄数量最大是61440，也可以[手动设置这个数值](https://huizhou92.com/content/files/2026/09/mac-set-maximum-connection-and-port-parameters-1a3b114aad49-1.html)。go 程序会默认打开 `stderr`，`stdout`，`stdin` 三个文件句柄，一个进程最多能够打开61437 文件，多了就会报错。

### http.Response.Body.Close()

Go 语言有一个 比较“知名”的bug，相信您一定看到过：如果我们忘记关闭 http 请求的body 的话，会导致内存泄露，比如下面的代码。  
[https://gist.github.com/hxzhouh/1e63ef82a1088ac378384e30651b20c9](https://gist.github.com/hxzhouh/1e63ef82a1088ac378384e30651b20c9?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/1e63ef82a1088ac378384e30651b20c9?ref=huizhou92.com)  
code 2： http body 没有关闭导致内存泄露。

```go
func makeRequest() {  
    client := &http.Client{}  
    req, err := http.NewRequest(http.MethodGet, "http://localhost:8081", nil)  
    res, err := client.Do(req)  
    if err != nil {  
       fmt.Println(err)  
    }  
    _, err = ioutil.ReadAll(res.Body)  
    // defer res.Body.Close()  
    if err != nil {  
       fmt.Println(err)  
    }  
}

```

关于这个问题您可以参考下面的文章了解更多信息，后面如果有时间的话，我们从头梳理一下 `net/http`

- # [is resp.Body.Close() necessary if we don't read anything from the body?](https://stackoverflow.com/questions/18598780/is-resp-body-close-necessary-if-we-dont-read-anything-from-the-body?ref=huizhou92.com)
- [https://manishrjain.com/must-close-golang-http-response](https://manishrjain.com/must-close-golang-http-response?ref=huizhou92.com)

## 字符串/slice 导致内存泄露

虽然 `Go spec` 并没有说明一个字符串表达式的结果（子）字符串和原来字符串是否共享一个内存块 但编译器确实让它们共享一个内存块，而且很多标准库包的函数原型设计也默认了这一点。这是一个好的设计，它不仅节省内存，而且还减少了CPU消耗。 但是有时候它会造成**暂时性**的内存泄露。  
Code 3: 字符串导致内存泄露。  
[https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16](https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16?ref=huizhou92.com)

![字符串导致内存泄露](https://images.hxzhouh.com/blog-images/2026/05/cc08c81d30f9aab539e0999e0f88d4de.png)

为防止`createStringWithLengthOnHeap` 临时性内存泄露，我们可以使用`strings.Clone()`  
Code 4 ： 使用`strings.Clone()` 避免临时内存泄露。

```go
func Demo1() {  
    for i := 0; i < 10; i++ {  
       s := createStringWithLengthOnHeap(1 << 20) //1M  
       packageStr1 = append(packageStr1, strings.Clone(s[:50]))  
    }  
}

```

这样就不会导致临时性内存泄露了。

## Goroutine Leak

### Goroutine Handler

绝大部分内存泄露的原因是因为goroutine泄露，比如下面的例子，很快将会内存耗尽 而导致`OOM`  
Code 5: goroutine handler

```go
for {  
       go func() {  
          time.Sleep(1 * time.Hour)  
       }()  
    }  
}

```

### Channel

[channel](https://go.dev/ref/spec?ref=huizhou92.com#Channel%5Ftypes) 的使用错误也很容易导致 `goroutine` 泄露，  
对于无缓冲的channel，必须要等到生产者和消费者全部就绪后，才能往channel写数据，否则将会阻塞。下面的例子因为`Example` 提前退出导致协程泄露。  
Code 6: 不合理使用无缓冲channel 导致`goroutine`泄露

```go
func Example() {
    a := 1
    c := make(chan error)
    go func() {
        c <- err
        return
    }()
    // Example 在这里退出，导致协程泄露。
    if a > 0 {
        return
    }
    err := <-c
}

```

只需要改成有缓冲的channel 就能解决这个问题 `c:= make(chan error,1)`  
还有一个典型的例子就是channel range 误用。  
Code 7: 不合理使用`range` 导致`goroutine`泄露

```go
func main() {  
    wg := &sync.WaitGroup{}  
    c := make(chan any, 1)  
    items := []int{1, 2, 3, 4, 5}  
    for _, i := range items {  
       wg.Add(1)  
       go func() {  
          c <- i  
       }()  
    }  
    go func() {  
       for data := range c {  
          fmt.Println(data)
          wg.Done()    
        }
       fmt.Println("close")  
    }()  
    wg.Wait()  
    time.Sleep(1 * time.Second)  
}

```

channel 可以使用 `range` 迭代 .但是一旦读取不到内容，range 就会等待 channel 的写入，而 range 如果正好在 goroutine 内部，这个 goroutine 就会被阻塞，造成泄露。正确的做法是: 在`wg.Wait()` 后面 close channel.

## runtime.SetFinalizer误用

如果两个对象都设置了 `runtime.SetFinalizer` 并且他们之间存在 "循环引⽤" ，那么这两个对象将会泄露，即时他们不再使用，`GC` 也不会回收他们。  
关于 `runtime.SetFinalizer` 的更多内容，可以参考我的[另外一篇文章](https://huizhou92.com/content/files/2026/09/decrypt-go-runtime-setfinalizer-adf19cb5fdcb-5.html)

## time.Ticker

这是go 1.23 版本之前的问题了， 如果我们不调用`ticker.Stop()` 会造成内存泄露。go 1.23 已经不会造成泄露了 [https://go.dev/doc/go1.23#timer-changes](https://go.dev/doc/go1.23?ref=huizhou92.com#timer-changes)

## Defer

我们一般习惯在`defer` 中释放资源 `defer` 函数本身不会导致内存泄露。但是它的两个机制可能会导致内存临时性泄露。

1. 执行时间，`defer` 总是在函数结束的运行。如果您的函数运行时间过长，或者永远不会结束，那么您在defer 中释放的资源可能，很久都不会被释放，或者永远都不被释放。
2. `defer` 本身也需要占用内存，每个 `defer` 都会在内存中添加一个调用点。如果您在循环中使用defer，有可能会导致临时性的内存泄露。  
Code 8: `defer` 导致 内存临时泄露

```go
func ReadFile(files []string) {  
    for _, file := range files {  
	  f, err := os.Open(file)  
	  if err != nil {  
		 fmt.Println(err)  
		 return  
	  }  
	  // do something  
	  defer f.Close()  
    }  
}

```

比如上面的代码，不仅仅可能会导致 `defer` 临时内存泄露，还可能会导致`too many open files`  
不要痴迷于使用`defer` 除非你觉得代码你的代码可能会`panic` ，否则及时关闭文件是一个更好的选择。

## 总结

本文列举了几种可能会导致go 内存泄露的行为，同时 Goroutine 内存泄漏是 Go 语言最容易发生的内存泄漏情况，它通常伴随着错误地使用 goroutine 和 channel等。而 channel 的特殊用法如 select 和 range 又让 channel 阻塞变得更加隐蔽不易发现，进而增加排查内存泄漏的难度。  
遇到内存泄露问题，我们可以通过`pprof` 帮助我们快速的定位问题，希望我们每个人都能写出健壮的代码。

## 参考资料

[https://go101.org/article/memory-leaking.html](https://go101.org/article/memory-leaking.html?ref=huizhou92.com)