以下服务器代码:
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左右。
思考?我认为这不是预料/我做错了什么?
答案 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
将在后台分配更多(更少)缓冲区。