【这是一道送命题】使用字符串传值居然也不会释放原始字符串的内存地址!!!
0

这个问题陆陆续续查了一周才实锤到内存泄露的点

问题的开端是这样的,以前大哥走了,我接了这个项目发现居然在内存泄露,每天要重启好几次,决定解决一下,没想到解决了一周。

项目使用了一个包叫 gjson (链接),我们有两个缓存一个叫cache1 gjson.Result{},一个叫 cache2 map[string]string 。cache2 的数据来源是一个 URL 每分钟更新一次使用 gjson 解析 接口返回的 json 数据,cache1 的数据来源是从 cache2 可以匹配上的数据通过 gjson 获取的字符串就放到 cache 1 map 中(当 cache1 把 cache2 键全匹配完了以后就不会再更新了。我也不知道这大哥怎么想的,顺手把这个 bug 也给修了)。

更新 cache2 的是一个 goroutine 60s 一次,匹配 cache1 又是一个 goroutine,所以呢 cache1 是从很多的 cache2 版本中拼凑而来的,因为 cache2 总更新。问题就来了,随着每次更新 cache2 我的内存占用就越来越大。使用 pprof 查看是在包内部出字节切片转字符串的时候开辟内存不能释放,如下

有图

我这一看这有问题啊,为什么我把一个从 gjson.Result.Get("key").String() 获取来的 string 赋值给一个 map[string]string,在 cache2 goroutine 后就不在使用这个 gjson.Result 变量了,而是创建了新的 gjson.Result ,为什么这个旧的 gjson.Result 的内存地址不能释放呢?Go里面不是字符串传值是完全拷贝吗?没道理啊?

在我研究了很久写了 N 个 demo 去排除各种情况后我开始看他的包是怎么给我的这个字符串,我看到了他给我的字符串是靠字符串位置获取的:

var cache2 string
var cache1 map[string]string    

cache2 = string([]byte) // 这个转换创建的内存空间不会被释放 []byte 相当于接口返回
cache1["key"] = cache2[0:2] // 这玩意居然是个 string 不是 slice

好了明白了。因为我 cache1 挂上了 cache2 字符串的某一小段字符串,所以整个 cache2 根本不会被释放。在随着不停的更新 cache2 ,就会越来越大,因为只要挂上一个字母他就不会释放了。妈了个卖批!!!(不好意思)字符串还有这样骚操作传值。

问题的解决方式有就是在开辟一个 []byte 把获取的字符串 append() 进去:

string(append([]byte(nil), gjson.Result.Get("name").String()...))

或者使用 strings.Builder{} 结构体,它里面的实现和上面的方法类似,不过版本低的 Go 没这个方法。

好了,出坑

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!