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