Golang程序内存占用过高如何优化
Golang程序内存占用过高如何优化 你好。
我正在尝试构建一个小型的HTTP API,用于接收文件并对其进行一些操作。我能够成功接收文件的base64编码,解码并将其保存到磁盘,但出于某些原因,我的程序在之后没有释放内存。
以下是服务器的完整代码:
package main
import (
"encoding/json"
"github.com/gorilla/mux"
"log"
"net/http"
"encoding/base64"
"os"
"io/ioutil"
"path"
"os/exec"
"fmt"
)
const progName = "dummy"
type Request struct {
FileBase64 string `json:"base64,omitempty"`
FileName string `json:"filename,omitempty"`
Auth string `json:"auth,omitempty"`
}
type Reply struct {
Success bool
Status string
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/put", processFile).Methods("POST")
log.Fatal(http.ListenAndServe("localhost:8000", router))
}
func processFile(w http.ResponseWriter, r *http.Request) {
var cmd Request
_ = json.NewDecoder(r.Body).Decode(&cmd)
log.Printf("request received")
// decode incoming data
dec, err := base64.StdEncoding.DecodeString(cmd.FileBase64)
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot decode base64:", err)
reply := Reply{false, "cannot decode base64"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("request decoded")
// create temp directory
// we need it to dump the input file and run AppImage on it.
tempDir, err := ioutil.TempDir("", progName+"-")
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot create temp directory:", err)
reply := Reply{false, "cannot create temp directory"}
json.NewEncoder(w).Encode(reply)
return
}
defer os.RemoveAll(tempDir)
log.Printf("created temp dir")
// create output file
zipFile, err := os.Create(path.Join(tempDir, cmd.FileName))
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot create output file:", err)
reply := Reply{false, "cannot create output file"}
json.NewEncoder(w).Encode(reply)
return
}
defer zipFile.Close()
log.Printf("output file created")
// write content
written, err := zipFile.Write(dec)
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot write output file:", err)
reply := Reply{false, "cannot write output file"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("content written")
// flush buffer
err = zipFile.Sync()
if err != nil {
log.Println(r.RemoteAddr, "-", "error while flushing buffer:", err)
reply := Reply{false, "error while flushing buffer"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("buffer flushed")
reply := Reply{true, fmt.Sprintf("written %d bytes", written)}
json.NewEncoder(w).Encode(reply)
}
为了向API发送文件,我使用以下命令:
(echo -n '{"filename": "test.zip", "base64": "'; base64 500M.bin; echo '"}') | curl -H "Content-Type: application/json" -d @- http://127.0.0.1:8000/put
当使用500MB文件进行测试时,服务器组件会占用大量内存,更重要的是,当函数processFile退出时,它不会释放这些内存。
对于我做错了什么,有什么想法吗?
非常感谢。
更多关于Golang程序内存占用过高如何优化的实战教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢,我已经添加了指令,但我的程序仍然没有释放内存。你知道可能是什么原因吗?
更多关于Golang程序内存占用过高如何优化的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你在 processFile 函数中缺少对 r.Body.Close() 的调用。
实际上,应该使用 defer r.Body.Close() 😊
另外:你没有检查错误。你不应该这样做。请检查所有的错误…(未来的你会感谢你的)
你如何测量内存使用情况?
你强制运行过垃圾回收吗?
即使垃圾回收器收集并清理了已使用的内存,它也只会在特定时间后(如果有的话)归还给操作系统。
我从未因此遇到过问题,但经常听说 Go 运行时不太愿意将内存归还给操作系统……
这是 Go 语言的本质特性
Go 内存分配器会预留一大块虚拟内存区域作为分配用的内存池。这个虚拟内存是特定 Go 进程独有的;这种预留操作不会剥夺其他进程的内存。
要查找分配给 Go 进程的实际内存量,请使用 Unix top 命令并查看 RES(Linux)或 RSIZE(Mac OS X)列。
https://golang.org/doc/faq#Why_does_my_Go_process_use_so_much_virtual_memory
实际上,文档指出您不需要关闭 r.Body。我仍然认为这是一种良好的风格,但如果您忘记了,也不会造成问题
http package - net/http - Go Packages
http 包提供了 HTTP 客户端和服务器的实现。
服务器将关闭请求体。…
问题出在您的代码中,dec 变量存储了整个解码后的文件内容(500MB),这个变量在函数结束时才会被垃圾回收。由于Go的垃圾回收不是立即执行的,内存看起来没有被立即释放。此外,在处理大文件时,一次性将整个文件加载到内存中会导致高内存占用。
以下是优化后的代码,通过流式处理避免将整个文件内容存储在内存中:
package main
import (
"encoding/json"
"github.com/gorilla/mux"
"log"
"net/http"
"encoding/base64"
"os"
"io"
"path"
"fmt"
)
const progName = "dummy"
type Request struct {
FileBase64 string `json:"base64,omitempty"`
FileName string `json:"filename,omitempty"`
Auth string `json:"auth,omitempty"`
}
type Reply struct {
Success bool
Status string
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/put", processFile).Methods("POST")
log.Fatal(http.ListenAndServe("localhost:8000", router))
}
func processFile(w http.ResponseWriter, r *http.Request) {
var cmd Request
if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil {
log.Println(r.RemoteAddr, "-", "cannot decode JSON:", err)
reply := Reply{false, "cannot decode JSON"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("request received")
tempDir, err := os.MkdirTemp("", progName+"-")
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot create temp directory:", err)
reply := Reply{false, "cannot create temp directory"}
json.NewEncoder(w).Encode(reply)
return
}
defer os.RemoveAll(tempDir)
log.Printf("created temp dir")
zipFilePath := path.Join(tempDir, cmd.FileName)
zipFile, err := os.Create(zipFilePath)
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot create output file:", err)
reply := Reply{false, "cannot create output file"}
json.NewEncoder(w).Encode(reply)
return
}
defer zipFile.Close()
log.Printf("output file created")
decoder := base64.NewDecoder(base64.StdEncoding, r.Body)
written, err := io.Copy(zipFile, decoder)
if err != nil {
log.Println(r.RemoteAddr, "-", "cannot write output file:", err)
reply := Reply{false, "cannot write output file"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("content written")
if err := zipFile.Sync(); err != nil {
log.Println(r.RemoteAddr, "-", "error while flushing buffer:", err)
reply := Reply{false, "error while flushing buffer"}
json.NewEncoder(w).Encode(reply)
return
}
log.Printf("buffer flushed")
reply := Reply{true, fmt.Sprintf("written %d bytes", written)}
json.NewEncoder(w).Encode(reply)
}
主要改进:
- 使用
base64.NewDecoder直接从请求体流式解码base64数据,避免将整个文件内容加载到内存。 - 使用
io.Copy将解码后的数据直接写入文件,减少内存缓冲。 - 替换
ioutil.TempDir为os.MkdirTemp(因为ioutil已弃用)。
这样,内存占用将显著降低,因为数据是流式处理而不是全部加载到内存中。垃圾回收也会更有效地管理内存。

