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

简介: Go 1.12 关于内存释放的一个"改进"

概述:


“改进” 加引号,因为社区反馈问题多多 弊大于利, 于是Go 1.16又改了回去…

问题包括但不仅限于:

  • 引发用户体验的问题:Go issues 上总是出现以为内存泄露,但其实只是未满足条件,内存没有马上释放的案例。
  • 混淆统计信息和监控工具的情况:在 Grafana 等监控上,发现容器进程内存较高,释放很慢,告警了,很慌。
  • 导致与内存使用有关联的个别管理系统集成不良:例如 Kubernetes HPA ,或者自定义了扩缩容策略这类模式,难以评估。
  • 挤压同主机上的其他应用资源:并不是所有的 Go 程序都一定独立跑在单一主机中,自然就会导致同一台主机上的其他应用受到挤压,这是难以评估的。

(参考自 Go1.16 新特性:详解内存管理机制的变更,你需要了解)

出现这个问题 需要同时满足 Go版本在 1.12~1.15之间(都是闭区间),同时是Linux系统且内核版本>=4.5

通过设置环境变量 GODEBUG:madvdontneed=1即可关闭该功能


Go 1.12~Go 1.15


Go支持两种内存回收方式,即 MADV_DONTNEEDMADV_FREE

其中MADV_FREE

Go 1.12

版本引入,官网上的介绍如下:

1
On Linux, the runtime now uses MADV_FREE to release unused memory. This is more efficient but may result in higher reported RSS. The kernel will reclaim the unused data when it is needed. To revert to the Go 1.11 behavior (MADV_DONTNEED), set the environment variable GODEBUG=madvdontneed=1.

大意就是使用MADV_FREE方式,程序内存不会立刻回收,即

RSS值

不会立刻下降,只有当OS内存紧缺时才会回收Go程序的内存返回给OS;

而Go 1.11以及之前的版本默认采用的是 MADV_DONTNEED方式,程序RSS值下降很快。因此如果需要使程序内存占用下降很快的话,可设置环境变量GODEBUG=madvdontneed=1

注:Go 1.12~1.15版本的这项功能, 仅限于 Linux平台; 且需要内核在 4.5及之后的版本,才默认使用MADV_FREE方式。 如果不支持会用退用之前默认的MADV_DONTNEED

runtime/mem_linux.go源码里注释如下:

go

123456789101112
var advise uint32if debug.madvdontneed != 0 { advise = _MADV_DONTNEED} else { advise = atomic.Load(&adviseUnused)}if errno := madvise(v, n, int32(advise)); advise == _MADV_FREE && errno != 0 {  // MADV_FREE was added in Linux 4.5. Fall back to MADV_DONTNEED if it is  // not supported. atomic.Store(&adviseUnused, _MADV_DONTNEED) madvise(v, n, _MADV_DONTNEED)}

image.png


参考:

Go 1.12中出现的top命令RES参数异常增高的问题

Go进程的HeapReleased上升,但是RSS不下降造成内存泄漏?—- 并不是内存泄露

Go 1.12 关于内存释放的一个改进—并不是内存泄露


Go 1.16又改了回去


Go 1.16

中,对这个问题又做了优化,

在 Linux 上,runtime 现在默认会迅速地(使用 MADV_DONTNEED)向操作系统释放内存,而不是在操作系统面临内存压力时(使用 MADV_FREE)惰性地释放内存。

这意味着像 RSS 这样的进程级内存统计信息将更准确地反映 Go 进程所使用的物理内存数量。因此 Go1.16 中,不再需要配置 GODEBUG=madvdontneed=1 来改善内存监控行为。

通过

123456789
func parsedebugvars() {    // defaults    debug.cgocheck = 1    debug.invalidptr = 1    if GOOS == "linux" {        debug.madvdontneed = 1    }  ...}

又 直接指定回了

debug.madvdontneed = 1

也算是Go Team走的一段弯路

参考&值得阅读:

列举一些 Go1.16 中可能对大家有影响的变化

Go1.16 新特性:详解内存管理机制的变更,你需要了解

Go 1.16中值得关注的几个变化


作者:fliter

链接:https://juejin.cn/post/6949039357525229604

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

目录
相关文章
|
7月前
|
存储 Go iOS开发
掌握Go语言:探索Go语言指针,解锁高效内存操作与动态数据结构的奥秘(19)
掌握Go语言:探索Go语言指针,解锁高效内存操作与动态数据结构的奥秘(19)
|
7月前
|
存储 缓存 安全
Go语言内存模型深度解析
【2月更文挑战第16天】Go语言以其简洁的语法、强大的并发编程能力和高效的内存管理而备受开发者青睐。本文将对Go语言的内存模型进行深度解析,探讨其内存布局、内存分配与回收机制以及内存安全等方面的内容,帮助读者更好地理解和应用Go语言的内存管理特性。
|
1月前
|
编译器 Go
探索 Go 语言中的内存对齐:为什么结构体大小会有所不同?
在 Go 语言中,内存对齐是优化内存访问速度的重要概念。通过调整数据在内存中的位置,编译器确保不同类型的数据能够高效访问。本文通过示例代码展示了两个结构体 `A` 和 `B`,尽管字段相同但排列不同,导致内存占用分别为 40 字节和 48 字节。通过分析内存布局,解释了内存对齐的原因,并提供了优化结构体字段顺序的方法,以减少内存填充,提高性能。
41 3
|
1月前
|
Java 编译器 测试技术
go语言避免不必要的内存分配
【10月更文挑战第18天】
48 1
|
1月前
|
存储 算法 Java
Go语言的内存管理机制
【10月更文挑战第25天】Go语言的内存管理机制
32 2
|
7月前
|
算法 Java Go
Go vs Java:内存管理与垃圾回收机制对比
对比了Go和Java的内存管理与垃圾回收机制。Java依赖JVM自动管理内存,使用堆栈内存并采用多种垃圾回收算法,如标记-清除和分代收集。Go则提供更多的手动控制,内存分配与释放由分配器和垃圾回收器协同完成,使用三色标记算法并发回收。示例展示了Java中对象自动创建和销毁,而Go中开发者需注意内存泄漏。选择语言应根据项目需求和技术栈来决定。
|
4月前
|
存储 安全 编译器
Go 内存分布
该文章深入分析了Go语言中值的内存分布方式,特别是那些分布在多个内存块上的类型,如切片、映射、通道、函数、接口和字符串,并讨论了这些类型的内部结构和赋值时的行为,同时指出了“引用类型”这一术语在Go中的使用可能会引起的误解。
57 5
Go 内存分布
|
4月前
|
存储 安全 Go
Go 中的指针:了解内存引用
Go 中的指针:了解内存引用
|
4月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
4月前
|
缓存 编解码 测试技术
使用Go实现健壮的内存型缓存
使用Go实现健壮的内存型缓存
72 2