Golang Go语言中求教 os.readfile 内存溢出的问题

发布于 1周前 作者 yibo5220 来自 Go语言

求教一个内存溢出的问题 pprof 显示在 os.readfile 上有大量内存占用无法释放 请问我下面的写法具体哪里有问题 已经尝试很多办法,都没效果 恳请赐教

var GMap=map[string]string

func DomainMapLoadFromFile() { GMap=make(map[string]string,10) //尝试解决内存溢出 fileStr := ReadAll(“DataMap”, “CacheMap”) contentStrArr := strings.Split(*fileStr, “\n”) contentLen := len(contentStrArr) contentArr := make([]string, contentLen) copy(contentArr, contentStrArr) var wg sync.WaitGroup wg.Add(contentLen) for i := range contentArr { go func(content string) { infoArr := strings.Split(content, “|”) var deviceId int64 l := len(infoArr) if l == 2 { data1 = infoArr[0] data2 = infoArr[1] } else { fmt.Println(“不符合长度 5-6 的数据,content:” + content) wg.Done() return } GMap[data1]=data2 wg.Done() }(contentArr[i]) } wg.Wait() }

func ReadAll(fileName, dirName string) *string { content, _ := os.ReadFile(GetFilePathPWD(fileName, dirName)) contentStr := string(content) return &contentStr }


Golang Go语言中求教 os.readfile 内存溢出的问题

更多关于Golang Go语言中求教 os.readfile 内存溢出的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

25 回复

怎么复现的?

更多关于Golang Go语言中求教 os.readfile 内存溢出的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


#1 线上系统 pprof 比较了 6 个小时间隔的内存数据,这部分增长了 3G 的内存

ReadAll 里面应该返回 string ,这不会增加开销,用指针反而会增加 GC 的开销。用 copy 拷贝数组,应该不会拷贝字符串吧,strings.Split 函数返回的子串都是对原字符串的引用。尝试在向里面的匿名函数传递数组元素时,使用 strings.Clone() 拷贝一份子串

var GMap=map[string]string

func DomainMapLoadFromFile() {
GMap=make(map[string]string,10)
//尝试解决内存溢出
fileStr := ReadAll(“DataMap”, “CacheMap”)
contentStrArr := strings.Split(*fileStr, “\n”)
var wg sync.WaitGroup
wg.Add(contentLen)
for i := range contentStrArr {
go func(content string) {
infoArr := strings.Split(content, “|”)
var deviceId int64
l := len(infoArr)
if l == 2 {
data1 = infoArr[0]
data2 = infoArr[1]
} else {
fmt.Println(“不符合长度 5-6 的数据,content:” + content)
wg.Done()
return
}
GMap[data1]=data2
wg.Done()
}(strings.Clone(contentStrArr[i]))
}
wg.Wait()
}

func ReadAll(fileName, dirName string) string {
content, _ := os.ReadFile(GetFilePathPWD(fileName, dirName))
contentStr := string(content)
return contentStr
}

#3 感谢指点,我改下试试

也可以选择在
data1 = infoArr[0]
data2 = infoArr[1]
这里克隆两个字符串
data1 = strings.Clone(infoArr[0])
data2 = strings.Clone(infoArr[1])
应该是 GMap 长期持有子串,造成整个字符串无法被 GC ,尝试在长期持有子串的地方克隆一下子串

#6 有道理!感谢!受教了,我改好上线跑一段时间看看

问题解决,内存稳定了,感谢

嗯,这应该是经典的子串内存泄漏问题。

字符串的子串不回收会导致整个字符串不回收。

问题解决了,那只好吐槽一下这段代码了…
1. 一次读完整个文件有内存爆炸的风险,建议使用 bufio 逐行处理
2. 每行放到一个 goroutine 里切分还要加锁,真不如顺序处理快……这样写又复杂又慢又容易错

谢谢指导,因为数据最终要放到内存作为常驻缓存,所以逐行读取和一次性读取区别不大;切分处理速度比顺序处理快 6 倍,配置是 12 核心 24 线程虚拟机,也是优化过才成了这个样子,第一版是流式顺序处理,载入一次 1G 文件要 1 分钟多,无法忍受,改为多线程变成 10-16 秒了

是的,之前不明白此处该使用深拷贝,这次学习了


一次读取你要用掉 2 倍内存
处理速度这个我不太相信,要不把顺序处理的代码也贴一下,还有文件内容

楼上怎么一唱一和的。。。

有几点:
1. 代码有多处基础的语法错误
2. ReadAll 为什么要返回 str ptr ?
3. map 并发操作不安全
4. 你这个需求可以流式处理,不需要首先就 load 所有数据到内存
5. GetFilePathPWD 可以 path.Join ,当然可能你有特殊的需求
6. 值拷贝通常比指针引用占用更多的内存。

当然你这确实可能存在溢出的问题,大概是这样:一个很大的字符串 str ,只把它的一部分放进 map[1]=str[0:3]后,导致这个大的 str 的其他部分不能被回收。我不太清楚 gogc 对这种情况是咋做的。
如果上面假设成立,解决办法也很简单,用[]byte 从文件读,存入 map 的时候 map[string(data1)] = string(data2)

1 、截取了片段代码,部分是为了表达清楚逻辑构造的。
2 、最开始返回的 str ,但是内存溢出,就不断尝试调整
3 、实际使用的是带分区读写锁的 map ,还是为了逻辑简单直接构造了一个 map
4 、服务器内存比较大,直接读取实测速度更快,就改为这个版本了
5 、该函数底层就是 path.Join ,只是因为路径比较复杂,要动态获取,单独封装了一个函数
6 、指针引用再搭配分区 map ,会产生一些不可预料的修改错误,所以还是用值传递比较多了

最后,使用 string()转换不能解决溢出问题,使用 strings.clone 深拷贝才可以

感谢这么多较真的朋友!

string([]byte) 就是重新分配的内存,无论是 header 还是数据本身;和之前不存在任何引用关系

好的,那回头我再测试下

我想知道是不是因为你用 map 存储着 content 的引用导致了这个 content 一直没被释放掉

var GMap=map[string]string
这句能编译过?

更正 var GMap=map[string]string{}

存的都是值,没有使用指针

data1 和 data2 都是 content 的子串,本质就是指针指向了 content

哦,此处应该用 strings.clone 复制一份

在Go语言中,使用os.ReadFile函数读取整个文件内容到内存中时,如果文件非常大,确实可能导致内存溢出(即内存不足错误)。这是因为os.ReadFile会一次性将文件内容全部加载到内存中,形成一个大的字节切片([]byte)。

要解决这个问题,可以考虑以下几种方法:

  1. 使用os.Openbufio.Reader逐块读取: 使用os.Open打开文件,然后通过bufio.NewReader创建一个带缓冲区的读取器,利用Read方法或ReadLine方法(根据文件内容格式选择)逐块读取文件内容。这样可以在不占用过多内存的情况下处理大文件。

  2. 流式处理: 如果文件内容需要处理后再输出或存储,可以考虑实现一个流式处理器,边读取边处理,避免整个文件内容同时存在于内存中。

  3. 检查文件大小: 在读取文件之前,可以先使用os.Stat获取文件信息,检查文件大小,如果超出预期范围,则提前处理或报错。

  4. 增加内存限制: 虽然这不是直接解决内存溢出的方法,但可以通过调整Go运行时的内存限制(如设置GOMAXPROCSGOGC等环境变量)来尝试优化内存使用。

总之,处理大文件时应避免一次性将整个文件加载到内存中,而应采用逐块读取或流式处理的方法,以有效避免内存溢出问题。

回到顶部