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

时间:2015-06-05 02:32:51

标签: memory-management memory-leaks go

以下服务器代码:

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/

在darwin / amd64和linux / amd64的Go 1.4.2中,large-file约为80MB似乎存在某种形式的内存泄漏。

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

如果我一直打电话给curl,那么该进程的内存使用量会随着时间的推移而变慢,最终似乎在我的计算机上保持300MB左右。

思考?我认为这不是预料/我做错了什么?

1 个答案:

答案 0 :(得分:9)

如果内存使用率停留在"最大值",我就不会真正称之为内存泄漏。我宁愿说GC不急切而且懒惰。或者,如果频繁重新分配/需要,或者只是不想实际释放内存。如果它确实是内存泄漏,则使用的内存不会停留在300 MB。

r.FormFile("file")将调用Request.ParseMultipartForm(),32 MB将用作maxMemory参数的值(defaultMaxMemory变量的值request.go 1}})。由于您上传了一个较大的文件(80 MB),因此至少会创建一个大小为32 MB的缓冲区 - 最终(这在multipart.Reader.ReadFrom()中实现)。由于bytes.Buffer用于读取内容,因此读取过程将从一个小的或空的缓冲区开始,并在需要更大的缓冲区时重新分配。

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

这样做几次,如果没有GC立即释放分配的缓冲区,您最终可以轻松获得300 MB。如果有足够的可用内存,那么GC就没有压力来释放内存。您认为它增长相对较大的原因是因为在后台使用了大缓冲区。您是否会在上传1MB文件时也这样做,您可能不会遇到这种情况。

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

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

将在后台分配更多(更少)缓冲区。