多部分表单上传+golang中的内存泄漏?

Multipart form uploads + memory leaks in golang?

以下服务器代码:

package main

import (
  "fmt"
  "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
  file, _, err := r.FormFile("file")
  if err != nil {
    fmt.Fprintln(w, err)
    return
  }
  defer file.Close()

  return
}

func main() {
  http.ListenAndServe(":8081", http.HandlerFunc(handler))
}

成为运行然后调用它:

curl -i -F "file=@./large-file" --form hello=world http://localhost:8081/

large-file 大约 80MB 似乎在 darwin/amd64 和 linux/amd64.

上的 Go 1.4.2 中存在某种形式的内存泄漏

当我连接 pprof 时,我看到 bytes.makeSlice 在调用该服务几次后使用了 96MB 的内存(最终在我上面的代码中被 r.FormFile 调用)。

如果我继续调用 curl,进程的内存使用量会随着时间的推移而变慢,最终似乎会在我的机器上停留在 300MB 左右。

想法?我认为这不是预期的/我做错了什么?

如果内存使用停滞在 "maximum",我不会真正称之为内存泄漏。我宁愿说 GC 不急于和懒惰。或者只是不想在经常重新分配/需要的情况下物理释放内存。如果真的是内存泄漏,使用的内存不会止于 300 MB。

r.FormFile("file") 将导致调用 Request.ParseMultipartForm(), and 32 MB will be used as the value of maxMemory parameter (the value of defaultMaxMemory variable defined in request.go). Since you upload a larger file (80 MB), a buffer of size 32 MB at least will be created - eventually (this is implemented in multipart.Reader.ReadFrom()). Since bytes.Buffer 用于读取内容,读取过程将从一个小的或空的缓冲区开始,并在需要更大的缓冲区时重新分配。

缓冲区重新分配的策略和缓冲区大小取决于实现(并且还取决于来自请求的 read/decoded 块的大小),但只是粗略地想象一下,像这样:0 字节、4 KB、16 KB、64 KB、256 KB、1 MB、4 MB、16 MB、64 MB。同样,这只是理论上的,但说明总和甚至可以增长超过 100 MB,只是为了读取内存中文件的前 32 MB,此时将决定它将在文件中 moved/stored。有关详细信息,请参阅 multipart.Reader.ReadFrom() 的实现。这合理地解释了 96 MB 的分配。

这样做几次,如果 GC 不立即释放分配的缓冲区,您很容易就会得到 300 MB。并且如果有足够的空闲内存,GC 也没有压力去抓紧释放内存。您看到它变得相对较大的原因是因为在后台使用了大缓冲区。如果你上传一个 1MB 的文件,你会做同样的事情吗,你可能不会遇到这种情况。

如果这对您很重要,您也可以使用较小的 maxMemory 值手动调用 Request.ParseMultipartForm(),例如

r.ParseMultipartForm(2 << 20) // 2 MB
file, _, err := r.FormFile("file")
// ... rest of your handler

这样做会在后台分配更小(和更少)的缓冲区。