> 这个问题陆陆续续查了一周才实锤到内存泄露的点
问题的开端是这样的,以前大哥走了,我接了这个项目发现居然在内存泄露,每天要重启好几次,决定解决一下,没想到解决了一周。
项目使用了一个包叫 [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 没这个方法。
好了,出坑
有疑问加站长微信联系(非本文作者))