FreeBSD服务中的Golang内存泄漏问题
FreeBSD服务中的Golang内存泄漏问题 使用 GoFiber 运行一个 HTTP 服务。在 Linux 机器的终端中运行此代码时,垃圾回收(GC)工作正常,内存会被释放。但在 FreeBSD 上作为启动服务运行时,内存不会被释放,并且会发生泄漏。

import (
"bytes"
"fmt"
"runtime"
"github.com/valyala/fastjson"
"github.com/xuri/excelize/v2"
)
func convert_json_to_excel(request_data *fastjson.Value, buffer_pointer *bytes.Buffer) error {
var err error
// Remove redundant "data" key passed from body
json_values := request_data.Get("data")
if json_values == nil {
err = fmt.Errorf("submitted json body must contain desired excel data in a key named \"data\": %d", err)
fmt.Println(err)
return err
}
json_values_array, _ := json_values.Array()
// Initial no longer needed
json_values.Del("data")
// Create a working temporary Excel "file"
excel_file := excelize.NewFile()
defer excel_file.Close()
sheet_index, _ := excel_file.NewSheet("Sheet1")
excel_file.SetActiveSheet(sheet_index)
//Embolden the first row
header_style, _ := excel_file.NewStyle(&excelize.Style{
Font: &excelize.Font{
Bold: true,
},
})
header_row_opts := excelize.RowOpts{StyleID: header_style}
stream_writer, _ := excel_file.NewStreamWriter("Sheet1")
// Widen all used columns based on headers
header_row, _ := json_values_array[0].Array()
stream_writer.SetColWidth(1, len(header_row), 30)
// Iterate through data and write to file
for row := 0; row < len(json_values_array); row++ {
active_row, _ := json_values_array[row].Array()
//fmt.Printf("%s\n", active_row)
// Streamwriter is very performant, use of it requires type []interface{}
interface_row := make([]interface{}, len(active_row))
for cell := 0; cell < len(active_row); cell++ {
// Free working memory
json_values_array[row].Del(fmt.Sprintf("%d", cell))
interface_row[cell] = active_row[cell].Get().MarshalTo(nil)
}
first_cell := fmt.Sprintf("A%d", (row + 1))
if row == 0 {
stream_writer.SetRow(first_cell, interface_row, header_row_opts)
} else {
stream_writer.SetRow(first_cell, interface_row)
}
}
stream_writer.Flush()
_, err = excel_file.WriteTo(buffer_pointer)
if err != nil {
err = fmt.Errorf("write fail: %d", err)
fmt.Println(err)
return err
}
// Force GC
runtime.GC()
return nil
}
更多关于FreeBSD服务中的Golang内存泄漏问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
哦,这很有趣。我原以为你可能有一些独立的、针对BSD的特定代码来让它作为服务运行,但实际上你构建的是相同的代码,而它在BSD上的行为却不同?这或许是一个在主要代码仓库中提交问题的好候选案例。
更多关于FreeBSD服务中的Golang内存泄漏问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
感谢回复,
以下是调用 convert_json_to_excel 的处理函数,此服务中的 main() 函数将仅用于初始化 API 路由等:
func handle_excel_v1(fiber_context *fiber.Ctx) error {
json_values_root, err := fastjson.ParseBytes(fiber_context.Body())
if err != nil {
err = fmt.Errorf("parse data error: %d", err)
fmt.Println(err)
return nil
}
// Buffer is set for convert_json_to_excel to write bytes to
var excel_data bytes.Buffer
err = convert_json_to_excel(json_values_root, &excel_data)
if err != nil {
err = fmt.Errorf("conversion error: %d", err)
fmt.Println(err)
return nil
}
if json_values_root.Exists("file_path") {
file, _ := os.Create(fmt.Sprintf("%s.%s", string(json_values_root.GetStringBytes("file_path")), "xlsx"))
defer file.Close()
file.Write(excel_data.Bytes())
fiber_context.JSON(fiber.Map{
"success": true,
"file_path": string(json_values_root.GetStringBytes("file_path")),
})
} else {
fiber_context.Response().Header.Set("Content-Disposition", "attachment; filename=response.xlsx")
fiber_context.Write(excel_data.Bytes())
}
return nil
}
在FreeBSD上作为服务运行时出现内存泄漏,通常与Go运行时在特定操作系统下的内存管理行为差异有关。以下是几个关键点和可能的解决方案:
1. FreeBSD内存管理差异
FreeBSD的jemalloc与Linux的glibc malloc行为不同,可能导致内存碎片化问题。Go的GC在FreeBSD上可能不会立即将内存返还给操作系统。
// 添加内存监控和调试
import (
"runtime/debug"
"runtime"
)
func monitorMemory() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB", m.Alloc/1024/1024)
fmt.Printf("\tTotalAlloc = %v MiB", m.TotalAlloc/1024/1024)
fmt.Printf("\tSys = %v MiB", m.Sys/1024/1024)
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
2. 显式内存清理
在FreeBSD上需要更积极地清理资源:
func convert_json_to_excel(request_data *fastjson.Value, buffer_pointer *bytes.Buffer) error {
// 在函数开始时设置内存清理
debug.SetGCPercent(10) // 更频繁的GC
defer func() {
debug.SetGCPercent(100) // 恢复默认值
runtime.GC()
runtime.GC() // 双重GC确保清理
}()
// 原有代码...
// 显式清理大对象
json_values_array = nil
excel_file = nil
// 强制内存释放
debug.FreeOSMemory()
return nil
}
3. 使用对象池减少分配
var bufferPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 1024*1024)) // 1MB初始容量
},
}
func convert_json_to_excel(request_data *fastjson.Value) (*bytes.Buffer, error) {
buffer := bufferPool.Get().(*bytes.Buffer)
buffer.Reset()
defer func() {
buffer.Reset()
bufferPool.Put(buffer)
}()
// 使用buffer...
return buffer, nil
}
4. 优化excelize使用
// 创建全局样式池
var stylePool = sync.Pool{
New: func() interface{} {
return &excelize.Style{
Font: &excelize.Font{Bold: true},
}
},
}
func convert_json_to_excel(request_data *fastjson.Value, buffer_pointer *bytes.Buffer) error {
// 从池中获取样式
headerStyle := stylePool.Get().(*excelize.Style)
defer stylePool.Put(headerStyle)
// 使用样式...
header_style, _ := excel_file.NewStyle(headerStyle)
// 更早地释放json数据
for row := 0; row < len(json_values_array); row++ {
// 处理数据...
json_values_array[row] = nil // 显式置空
}
json_values_array = nil
return nil
}
5. FreeBSD特定的环境变量设置
在FreeBSD服务启动脚本中添加:
export GODEBUG=madvdontneed=1
export GOGC=50
export GOMEMLIMIT=256MiB
6. 完整的优化版本
func convert_json_to_excel(request_data *fastjson.Value, buffer_pointer *bytes.Buffer) error {
// FreeBSD上更激进的内存管理
debug.SetGCPercent(20)
defer func() {
debug.SetGCPercent(100)
runtime.GC()
debug.FreeOSMemory()
}()
json_values := request_data.Get("data")
if json_values == nil {
return fmt.Errorf("submitted json body must contain desired excel data in a key named \"data\"")
}
json_values_array, _ := json_values.Array()
// 立即释放原始引用
request_data = nil
excel_file := excelize.NewFile()
defer func() {
excel_file.Close()
excel_file = nil
}()
// 使用固定大小的切片避免重新分配
rowCount := len(json_values_array)
if rowCount == 0 {
return nil
}
// 分批处理数据
const batchSize = 100
for start := 0; start < rowCount; start += batchSize {
end := start + batchSize
if end > rowCount {
end = rowCount
}
// 处理批次数据...
for row := start; row < end; row++ {
// 处理逻辑...
// 及时释放
json_values_array[row] = nil
}
// 批次处理完后强制GC
if start%1000 == 0 {
runtime.GC()
}
}
// 最终清理
json_values_array = nil
json_values = nil
return nil
}
这些修改应该能缓解FreeBSD上的内存泄漏问题。主要策略包括:更频繁的GC、显式内存释放、使用对象池和分批处理大数据。


