Page 251 - 《软件学报》2026年第3期
P. 251
1214 软件学报 2026 年第 37 卷第 3 期
12. + existingBuf := findAvailableBuf(mb.availableBufs)
13. − if strictConstraint {
14. − mb.buf = mb.availableBufs[0]
15. + if existingBuf != nil {
16. + mb.buf = existingBuf
17. } else {
18. mb.buf = newBytesBuf(size)
19. }
20. }
L6-1: 不合理的回收机制导致待回收对象仍然被长期引用. 在自行实现的分配回收机制中, 如果回收机制不合
理, 可能导致待回收对象长时间被引用, 从而引发内存泄漏. 但是如果缓冲池没有限制存储的对象数量, 就会导致
被缓冲池回收的对象总是存在引用, 其生命期和缓冲池对象一致. 而由于缓冲池的生命期通常贯穿整个程序生命
期, 造成了待回收对象在程序运行期间无法被及时回收, 从而表现为内存泄漏. 代码 10 中第 1–9 行展示了 tidb 的
Issue#39331 抽象后的问题及修复. 问题发生在 tidb 自定义的 kvMemBuf 类型的缓冲池中, 缓冲池通过 Recycle 方
法回收对象, 但是问题代码中未能正确处理缓冲池中的对象回收. 具体来说, availableBufs 对 buf 进行了持久引用,
导致每次调用时, buf 所引用的对象无法及时被垃圾回收器回收, 直到 availableBufs 也不再被引用为止. 由于缓冲
池生命期较长, 回收过程较慢, 最终导致了内存泄漏.
L6-2: 分配时复用较少. 在某些内存分配管理策略中, 当分配新内存时, 如果没有充分利用已有的资源, 会导致
大量的内存浪费. 具体来说, 如果在分配缓冲区时, 仅当第 1 个可用缓冲区满足特定条件时才会进行复用, 而忽略
了其他可用缓冲区的情况, 结果就会导致新分配的缓冲区数量增加, 进一步导致内存占用量过大. 代码 10 中
第 10–20 行中, 修复前的代码采用了如上所述的策略, 造成了较大的空间浪费. 修复后的代码会在所有可用缓冲区
进行检查, 只有找不到合适的缓冲区时才会新分配, 大幅提高内存复用效率, 避免不必要的浪费.
结论 4. 内存泄漏问题在 Go 语言中仍然广泛存在, 并且种类繁多. 虽然 Go 语言实现了自动垃圾回收机制, 资
源的释放仍然需要开发者手动添加, 同时 defer 延迟机制的引入并没有从根本上解决内存泄漏的问题. 在内存和其
他资源释放函数的调用中, 依然存在逻辑错误、调用次数不准确以及误用的情况. 特别是在高并发环境下, 因并发
阻塞及不同协程间共享数据结构设计不当, 可能导致内存占用异常增大. 此外, 开发者在自行实现内存分配和回收
机制时, 由于垃圾回收的存在, 开发者更容易在自行实现的分配回收机制中无意间引入对可回收对象的长生命期
引用, 导致这些对象在运行时无法被垃圾回收机制及时清理.
4.3.2 无效内存地址或空指针解引用问题模式
本文采样得到了 30 个 Issues, 其中有 21 个确定为无效内存地址或空指针解引用问题. 本节对这 21 个 Issues
进一步分析. 结果显示, 在 Go 语言中, 无效内存地址或空指针解引用问题主要表现为如表 11 所示的类别及数量
分布.
表 11 无效内存地址或空指针解引用 NPD 问题的人工分类
NPD问题类 NPD问题子类 Issues数量
N1-1: 函数返回值指针在使用前没有检查是否为空 6
N1-2: 条件表达式使用指针前没有判断指针是否为空 2
N1: 指针使用前没有检查是否为空 10
N1-3: 指针变量在声明后没有初始化就直接使用 1
N1-4: 使用指针前非空判断条件不充分 1
N2: 某些操作的执行取决于指针是否为 nil N2-1: 当错误发生后指针没有被置为 nil 以无效化后续操作 1 1
N3: 杂项 N3-1: 杂项 10 10
总计 21

