Page 249 - 《软件学报》2026年第3期
P. 249
1212 软件学报 2026 年第 37 卷第 3 期
容量的结构体来实现, 其中数组指针指向底层数组中的某个元素. 切片表达式会修改数组指针的位置, 同时改变切
片的长度和容量; 但是它不会修改底层数组中对其他对象的引用关系. 这导致使用切片表达式创建新切片之后, 新
切片相比于原切片修改了数组指针的位置或者长度, 无法通过索引表达式合法访问特定对象, 但是底层数组对这
些特定对象的引用仍然存在. 这些特定对象即使之后不再被访问, 也需要等到底层数组被垃圾回收器回收时才能
[7]
被回收, 其生命期被误延长了, 导致内存泄漏. 代码 6 中第 14–19 行展示了 grpc-go 的 Issue#4758 的代码简化, 原
始代码尝试通过切片表达式 q.queue = q.queue[1:] 删除对 q.queue[0] 所指向对象的引用. 然而实际上 q.queue[0] 仍
然存在于新切片 q.queue 的底层数组中, 造成隐式的内存引用, 从而导致 q.queue[0] 引用的对象无法被及时回收,
导致内存泄漏.
● L3: 并发导致的内存占用量大. 在该问题类中包含两个小类, 模式如代码 7 所示.
代码 7. L3: 并发问题模式.
1. // L3-1: pingcap/tidb/issues/13883
2. type Handle struct {
3. ctx sessionctx.Context // can’t be shared
4. − sync.Mutex // can be shared
5. ... // can be shared
6. + *HandleShare
7. }
8. type HandleShare struct {
9. + sync.Mutex
10. + ...
11. }
12. // L3-2: pingcap/tidb/issues/32412
13. go func() { ch <- 2 }
14. ... // does not receive from ch before returning
15. + require.Equal(t, 2, <- ch)
16. return
L3-1: 协程独占的数据结构过多. 在高并发场景中, 当每个协程都独占一个数据结构时, 每创建一个新协程就
会为该数据结构分配内存. 这种设计会导致内存占用不断增加, 最终表现为内存泄漏. 代码 7 中第 1–11 行展示了
tidb 的 Issue#13883 [51] 的代码简化. 在该问题中, sync.Mutex 和其他结构体域没有被设计为可以在多个协程之间共
享, 导致每个协程都分配自己的内存空间, 造成内存使用量在高并发情况下急剧上升.
L3-2: 协程在阻塞时无法正常退出, 也可能导致内存泄漏. 代码 7 中第 12–16 行展示了 tidb 的 Issue#32412 [52]
的代码简化. 在该问题中, 由于测试函数没有对创建的协程中的 channel 进行接收操作, 导致协程阻塞在 channel
的发送操作上, 无法正常退出, 最终造成协程资源泄漏.
● L4: 依赖项内存泄漏. 依赖项的内存泄漏也是导致 Go 应用程序内存泄漏的一个重要原因. 在该问题类中包
含 2 个小类, 模式如代码 8 所示.
代码 8. L4: 依赖项内存泄漏问题模式.
1. // L4-2: pomerium/pomerium/pull/4650
2. − var zstdEncoder, _ = zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
3. + const zstdWindowSize = 8 << 10 // 8 kiB

