golang 内存回收的疑问

2021-09-07 15:12:46 +08:00
 flycloud

先贴代码:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	data := make(map[int32][]int32)
	for i := 0; i < 1024; i++ {
		msg := make([]int32, 1024 * 512, 1024 * 512)
		msg[0] = 0	//访问一下内存, 触发从内核真正分配内存
		data[int32(i)] = msg
	}
	fmt.Println(len(data))
    
	if true {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
		<-sig
	}
}

编译:

GODEBUG=madvdontneed=1 GOOS=linux GOARCH=amd64 go build

如上,分配了 1024 个内存占用 2MB 的 slice,放入了 map 中,总共 2GB 内存占用。程序启动后分配完就一直阻塞着,大概 3 分钟后内存占用从 2GB 多降低到 70MB 左右,表现上看是之前分配的 slice 被 gc 了。但是 map 没有删除操作,也没有置为 nil,难道 golang 的 gc 机制就是这样,发现后续没有再使用这个 map 就直接 gc 了,尽管还没有离开这个 map 所在的作用域?

4041 次点击
所在节点    Go 编程语言
40 条回复
Mohanson
2021-09-07 15:21:54 +08:00
靠作用回收内存的手段叫 RAII (c++, rust), Go 用的是引用计数, 原理不一样.
gamexg
2021-09-07 15:36:16 +08:00



```
if true {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
}
```

后加个 fmt.Print(data) 试试。
data 没在使用,编译器可能回优化掉了。
CRVV
2021-09-07 15:40:02 +08:00
> 难道 golang 的 gc 机制就是这样,发现后续没有再使用这个 map 就直接 gc 了,尽管还没有离开这个 map 所在的作用域?

对的,就是这样。
MoYi123
2021-09-07 16:05:39 +08:00
msg[0] = 0;

改成
for ii, _ := range msg {
msg[ii] = 0 //访问一下内存, 触发从内核真正分配内存
}

就是 2G 内存了

我感觉是 msg[0]这样写是只取了一页的内存,所以还有 70MB,要是 map 被 gc 了,应该不会用这多内存的。
flycloud
2021-09-07 16:28:49 +08:00
@gamexg 在阻塞代码之后再使用 data,内存肯定不会降低的(已验证)。所以肯定是因为 map 被 gc 了。

但是为什么是 3 分钟后内存才瞬间降低, 然后就一直占有着 70MB,就比较奇怪了。
flycloud
2021-09-07 16:31:10 +08:00
@MoYi123 效果是一样的,msg[0] = 0 只访问这一个数据,RES 内存就是 2GB,说明访问了之后就分配了全部的内存,而不是只分配了一页。
MoYi123
2021-09-07 16:35:57 +08:00
@flycloud 我跑这个代码的表现和你的完全不同。阻塞代码之后再使用 data,我这里也是 70MB.
flycloud
2021-09-07 16:41:44 +08:00
@MoYi123 代码确定是这样的么:
```
func main() {
data := make(map[int32][]int32)
for i := 0; i < 1024; i++ {
msg := make([]int32, 1024 * 512, 1024 * 512)
msg[0] = 0 //访问一下内存, 触发从内核真正分配内存
data[int32(i)] = msg
}
fmt.Println(len(data))

if true {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
}
fmt.Println(len(data))
}
```
一直阻塞着,我等了 10 分钟,还是 2GB 的内存。
MoYi123
2021-09-07 16:47:08 +08:00
是的,我环境是
go version go1.17 linux/amd64

Linux ubuntu 5.11.0-27-generic #29~20.04.1-Ubuntu SMP Wed Aug 11 15:58:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

windows 上也是一样。
jtgogogo
2021-09-07 16:51:36 +08:00
我这边一直都是 72M
flycloud
2021-09-07 16:51:36 +08:00
@MoYi123 我的是:
go version go1.17 darwin/amd64
运行环境是:centos,Linux 172-20-245-36 4.18.0-193.28.1.el8_2.x86_64

你的运行结果更神奇了啊,阻塞后还会使用 data 的被 gc 了导致内存降低了?
jtgogogo
2021-09-07 16:51:57 +08:00
MAC
PureWhiteWu
2021-09-07 16:55:15 +08:00
@flycloud 3 分钟后才降低是 sysmon 每两分钟执行一次强制 gc 导致的。
PureWhiteWu
2021-09-07 16:55:38 +08:00
@Mohanson go 不是引用计数,是三色标记法
flycloud
2021-09-07 17:02:55 +08:00
@jtgogogo 是的,我在 mac 下运行一直是 70MB 。

但是在 centos 下,编译运行,刚开始是 2GB,几分钟后降低为 70MB 。
flycloud
2021-09-07 17:03:59 +08:00
@PureWhiteWu 嗯,可以明确的是 data 被 gc 了,但是剩下的 70MB 是哪儿去了呢
tuxz
2021-09-07 17:07:48 +08:00
请问这种图是怎么生成的呢
ksco
2021-09-07 17:11:21 +08:00
不管是什么垃圾回收算法,一定是根据内存是否还被引用来判断是否应该被回收。data 在程序结束前一直保持着对 map 的引用,所以是不会被 GC 的。所以 data 一定不是被 “GC” 了。

我猜测是因为你的程序中只用到了一个大 slice 的一小部分,所以没有用到的部分可能是被 Go 优化器回收了?不过这个就纯属拍脑袋瞎猜了。
flycloud
2021-09-07 17:14:29 +08:00
flycloud
2021-09-07 17:15:09 +08:00
@ksco 不是这样哈,分配出来的 slice,全部遍历了,也是一样的结果。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/800407

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX