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

DukeAnn · · 996 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

> 这个问题陆陆续续查了一周才实锤到内存泄露的点 问题的开端是这样的,以前大哥走了,我接了这个项目发现居然在内存泄露,每天要重启好几次,决定解决一下,没想到解决了一周。 项目使用了一个包叫 [gjson](https://github.com/tidwall/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 查看是在包内部出字节切片转字符串的时候开辟内存不能释放,如下 ![有图](https://user-images.githubusercontent.com/15902491/49505830-893d4e00-f8b7-11e8-9bfa-5e287f1f27c6.png) 我这一看这有问题啊,为什么我把一个从 `gjson.Result.Get("key").String()` 获取来的 `string` 赋值给一个 `map[string]string`,在 cache2 goroutine 后就不在使用这个 `gjson.Result` 变量了,而是创建了新的 `gjson.Result` ,为什么这个旧的 `gjson.Result` 的内存地址不能释放呢?Go里面不是字符串传值是完全拷贝吗?没道理啊? 在我研究了很久写了 N 个 demo 去排除各种情况后我开始看他的包是怎么给我的这个字符串,我看到了他给我的字符串是靠字符串位置获取的: ```go 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 没这个方法。 好了,出坑

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

996 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传