开篇之前容许我先骂自己一句:

我就是个小丑,焯

省流版: 在环境变量中设置 MALLOC_TRIM_THRESHOLD_在 1 - 1_000_000 范围内

理论上这个方法适用于所有 Unix-based 系统

这个问题我首次遇到是在这里:记录一次使用在 Linux 上使用 C# 的 SkiaSharp 时遇到的内存泄漏问题

从那之后我一直错误的认为这个问题就是 SkiaSharp 没有处理好在 Linux 下时非托管资源的释放

今天群友说 SkiaSharp 发新 Release 辣 我满怀期待去更新看看修了没 结果当然是内存还在涨(

随手用 dotnet-counter 看了一眼 发现其实堆上没那么多东西 但是 Working Set 就是很大 一直下不来

dotnet-counter

突然就有一个想法 会不会是 Linux 上内存分配策略和 Windows 上不一样 就去随手搜了一下 搜到这两个issue:

Excessive use of memory on linux compared to windows
Memory leak on unix systems using dynamic code compilation

然后就破案了(

具体的 是由 KevinCathcart 提到的这段话 用 DeepL 翻译了一下:

所有的Linux进程都有一个默认的线性堆,控制着brk/sbrk系统调用。这是从过去的线性内存图时代遗留下来的。像大多数堆一样,在这里分配的内存不能总是被释放回操作系统,因为即使是一个长期存在的或泄漏的分配接近尾声也会阻止减少大小。Linux内核将这个空间表示为[heap]。

Glibc malloc也可以使用mmap进行大的分配,并且可以在mmapped中创建自己的区域,内存,通常分配给不同的线程,以减少跨线程的竞争。

在我的机器上发生的事情是,线性堆([heap])已经增长到一个很大的尺寸,但C库无法缩小它,因为靠近末端的一些(可能相当小)内存块要么仍在使用,要么已经被泄露了。堆的其余部分(在我的系统上为180-190MB)没有被泄露,但仍可供malloc使用以满足请求。当然,大多数管理程序对非管理堆的使用是有限的,所以实际上这些空间被浪费了。

我不确定究竟是什么在使用这些内存,但这可以解释为什么valgrind没有发现大的泄漏,因为它知道线性堆本身没有泄漏内存,而且malloc仍然能够使用它。

没想到这个问题要牵扯到 malloc 去了 过于高深 如果有空打算研究研究

先提供一个简单的修复方法: 在环境变量中设置 MALLOC_TRIM_THRESHOLD_

在issue中他给出的范围在 1 - 1_000_000 之间 但具体应该还要根据自己程序实际使用情况来设定

理论上这个方法适用于所有 Unix-based 系统

在设置了这个环境变量以后 Bot的内存使用量终于回归正常水平了 我哭了