【golang】特定版本Golang应用内存泄露问题处理

2022-05-17  本文已影响0人  Bogon

线上一个服务,启动后 RSS 随任务数增加而持续上升,但是过了业务高峰期后,任务数已经下降,RSS 却没有下降,而是维持在高位水平。那内存到底被谁持有了呢?

内存使用量(mem.rss)居高不下,且低峰期未下降,怀疑发生了内存泄漏现象。

排查

刚开始怀疑时内存泄漏,但是抓取 pprof heap 图观察后,未发现泄露问题,且内存分配符合预期;

发现内存使用虽然居高不下,但未呈上涨趋势,因此修改关键字为“go 内存占用居高不下”,发现有相同问题;

结论

好多gopher都遇到过的这个问题,如果你的Go服务版本是大于等于1.12并且小于1.16的话,大概率也会遇到这个问题。

问题来自于 GO 在将内存归还给操作系统时的内存释放策略,详情见官方 issues[2],以下做简单介绍。

GO 内存释放策略

不同策略的释放机制

MADV_DONTNEED:内核将会在合适的时机去释放内存,但进程的 RSS(常驻内存)将会立即减少。如果再次申请内存,内核会重新分配一块新的空间。

MADV_FREE:只能在 linux 内核版本 4.5 以上才能使用,此操作理论上只是打了一个标记位,只有在内核感觉到内存压力的时候才会将这些打标记的内存回收掉,分配给其他进程使用。这个策略下进程的 RSS 不会立即减少。

不同策略的实际差别

理论上 MADV_FREE 效率要高一些,通过在页表中做标记的方式,延迟内存的分配和回收,可以提高内存管理的效率,毕竟内存的回收和分配都是会消耗系统性能的;

导致的 RSS 指标变化 MADV_DONTNEED 会导致进程 RSS 会有明显的下降;MADV_FREE 会导致进程 RSS 平稳在高峰,不会得到立即释放;

不同 GO 版本的释放策略

在 GO1.12 之前,默认均选择的 MADV_DONTNEED 策略进行内存回收

在 GO1.12~GO1.15,官方默认选择 MADV_FREE 策略进行内存回收

在 GO1.16 及之后,又改回了 MADV_DONTNEED 策略进行回收内存

在 GO1.12~GO1.15 且内核版本 4.5 以上,mem.rss 指标已经无法准确观测服务内存占用

解决方法

不解决,对程序性能有利,但是会降低一些可观测性

以下任一方法可以解决,但会损失一定性能 把 export GODEBUG=madvdontneed=1 写进服务 control.sh 脚本

升级 GO 版本至 1.16 及以上

使用 GODEBUG=madvdontneed=1 强制回退使用 MADV_DONTNEED,没有再出现内存泄漏问题。

参考

Go程序内存假泄漏是怎么回事 ?

https://zhuanlan.zhihu.com/p/408573636

Golang环境变量之GODEBUG

https://blog.haohtml.com/archives/21778

压测后go服务内存暴涨

http://soiiy.com/go/17114.html

Go 1.12 关于内存释放的一个改进

https://cloud.tencent.com/developer/article/1489460

上一篇下一篇

猜你喜欢

热点阅读