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) {
   245   246   247   248   249   250   251   252   253   254   255