Page 250 - 《软件学报》2026年第3期
P. 250
李清伟 等: Go 语言程序的内存性能与安全问题实证研究 1213
4. + var zstdEncoder, _ = zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedDefault),
zstd.WithWindowSize(zstdWindowSize))
L4-1: 在使用外部依赖项时, 如果依赖的库或版本存在内存泄漏问题, 且未及时更新到修复版, 可能会导
致内存泄漏. 问题通常出现在使用 Go 官方的依赖管理工具 (go.mod) 或 Git 的子模块管理 (git submodule) 时,
没有注意到依赖项本身的内存问题. 更新依赖库的版本, 或切换到没有内存泄漏的版本, 是解决此问题的有效
途径.
L4-2: 在使用依赖项时, 若没有调整默认的内存配置参数, 也可能导致内存占用过高, 从而引发内存泄漏. 代码 8
展示了 pomerium 的 Issue#4650 [53] 的代码简化. 问题修复通过配置 window size, 来优化内存使用, 减少不必要的内
存占用.
● L5: append 拷贝了内存占用量大的切片. 在 Go 语言中, append 操作可能会触发底层数组的重新分配, 特别
是在处理包含大量元素的切片时, 可能造成内存占用激增, 最终表现为内存泄漏甚至导致内存耗尽 (out of memory,
OOM). 代码 9 展示了 tidb 的 Issue#12472 [54] 的代码简化. 在该问题中, append 操作对 historySlice 这一包含大量元
素的切片进行了拷贝, 结果生成了两份内存占用量大的切片. 由于系统需要同时维护这两份切片的数据, 内存使用
量迅速飙升, 最终导致内存耗尽.
代码 9. L5: 拷贝大内存问题模式.
1. // L5-1 pingcap/tidb/pull/12472
2. − historySlice = GetHistory()
3. − newSlice = append(newSlice, historySlice ...) // copy large slice
4. + historySliceIter = GetHistoryIter() // use iterator instead of slice
5. − for job := range newSlice {
6. + for job := range historySliceIter.GetItem() {
7. + ...
8. + }
● L6: 分配回收速率不匹配. 在某些高性能 Go 应用 (如数据库系统 TiDB [55] ) 中, 开发者可能会自行实现分配
和回收机制. 然而由于分配和回收速率不匹配的问题, 可能导致内存使用量持续增长, 从而导致内存泄漏. 该类问
题模式如代码 10 所示.
代码 10. L6: 分配和回收速率不匹配问题模式.
1. // L6-1: pingcap/tidb/issues/39331
2. func (mb *kvMemBuf) Recycle(buf *bytesBuf) {
3. ...
4. + if len(mb.availableBufs) >= maxAvailableBufSize {
5. + mb.availableBufs = mb.availableBufs[1:]
6. + }
7. ...
8. mb.availableBufs = append(mb.availableBufs, buf)
9. }
10. // L6-2: pingcap/tidb/issues/39331
11. func (mb *kvMemBuf) AllocateBuf(size int) {

