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

Mic*_*ser 4 memory-leaks memory-management 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))
}
Run Code Online (Sandbox Code Playgroud)

正在运行然后调用它:

curl -i -F "file=@./large-file" --form hello=world http://localhost:8081/
Run Code Online (Sandbox Code Playgroud)

large-filedarwin/amd64和linux/amd64的Go 1.4.2中,大约80MB似乎有某种形式的内存泄漏.

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

如果我一直打电话curl,这个过程的内存使用量会随着时间的推移而变慢,最终看起来在我的机器上大约300MB.

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

icz*_*cza 9

如果内存使用停滞在"最大",我不会真的称之为内存泄漏.我宁愿说GC不急切而且懒惰.或者只是不经常重新分配/需要物理释放内存.如果它确实是内存泄漏,则使用的内存不会停止在300 MB.

r.FormFile("file")将导致调用Request.ParseMultipartForm(),并将32 MB用作maxMemory参数的值(defaultMaxMemory定义的变量的值request.go).由于您上传了一个更大的文件(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
Run Code Online (Sandbox Code Playgroud)

在后台分配更少(和更少)的缓冲区.