Go 内存泄漏,pprof 够用了吗?

简介: MOSN 是主要使用 Go 语言开发的云原生网络代理平台,在蚂蚁集团有着几十万容器的大规模生产应用。在这种大规模的应用中,经常会遇到各种内存问题,通常情况下 pprof heap profile 可以很好帮助分析问题。不过,有时候 pprof 也不够用,也就需要我们有更合适的工具了。

图片

文|朱德江(GitHub ID:doujiang24)

MOSN 项目核心开发者、蚂蚁集团技术专家

专注于云原生网关研发的相关工作

图片

本文 2651 字 阅读 8 分钟

MOSN 是主要使用 Go 语言开发的云原生网络代理平台,在蚂蚁集团有着几十万容器的大规模生产应用。在这种大规模的应用中,经常会遇到各种内存问题,通常情况下 pprof heap profile 可以很好帮助分析问题。不过,有时候 pprof 也不够用,也就需要我们有更合适的工具了。

Part.1--出生证 vs 暂住证

首先 pprof 确实很好用,设计实现也都很精巧,有兴趣的可以查看这篇《Go 语言 pprof heap profile 实现机制》[1]。

用 pprof 来分析内存泄漏,通常情况下是够用了,不过有时候,也会不够用。

这是为什么呢?因为 pprof 只是记录了内存对象被创建时的调用栈,并没有引用关系。也就是说,没有办法知道,内存对象是因为被谁引用了而导致没有被释放。对此,我的同事--烈元同学有一个很形象的比喻,pprof 只能看到出生证,却查不了暂住证。

Part.2--需要引用关系

有些场景下,我们知道了泄漏的内存是从哪里申请的,但是翻了半天代码,也搞不清楚内存为什么没有释放。比如,内存对象经过复杂的调用传递,或者复杂的内存池复用机制,又或者传给了某个不熟悉的第三方库,在第三方库中有非预期的使用……

在这些情况下,我们会有一个很直觉的想法是,想看看这些内存对象的引用关系。

Part.3--内存引用关系火焰图

内存引用关系火焰图,是一种内存对象引用关系的可视化方式,最早应用于 OpenResty XRay 产品。这个工具确实是内存分析神器,给不少的客户定位过内存问题,感兴趣的可以移步 OpenResty 官方博客[2]。

下图是由一个 MOSN 服务产生的,自下而上表示的是从 GC root 到 GC object 的引用关系链,宽度表示的是对象大小 (也包括其引用的对象的大小之和)

有了这样的可视化结果,我们就可以直观的看到内存对象的引用关系。

比如下图最宽的部分,表示的是 MOSN 中 cluster_manager 全局变量中引用的 cluster 内存对象:

https://github.com/mosn/mosn/blob/aecc93c4b2b4801e7992387f245fe9eefa45733d/pkg/upstream/cluster/cluster_manager.go#L82

图片

Part.4--实现原理

在生成火焰图之前,首先我们需要提取两个关键信息:

- 每个内存对象之间的引用关系;

- 每个内存对象的类型。

引用关系

获取引用关系比较简单,首先,我们可以在 heap 中找到所有的 GC 对象。然后遍历所有的对象,再结合 bitmap 信息,获取这个对象引用的其他对象。基本原理跟 GC mark 是类似的,虽然实现上很不一样,但因为这个是离线工具,可以简单粗暴的实现。

类型推导

Go 语言作为编译型静态语言,是不需要为每个内存对象存储类型信息的 (有点例外的是 interface) 。如果是动态类型语言,比如 Lua,则会方便很多,每个 GC 对象都存储了对象的类型。

所以,要获取每个对象的类型,还是比较麻烦的,也是投入时间最多的一块。当然,还是有解决办法的,简单来说就是做逆向类型推导,根据已知内存的类型信息,推导被引用的内存对象的类型信息。

这块还是比较复杂的,有兴趣的可以看这篇《Go 语言,如何做逆向类型推导》[3]的介绍。

生成过程

有了这两个关键信息之后,生成过程如下还是比较清晰的:

1. 获取所有的内存对象,包括类型、大小,以及他们之间的引用关系,形成一个图;

2. 从 root 对象出发,按照层次遍历,形成一棵树 (也就是剪枝过程,每个对象只能被引用一次)

3. 将这棵树的完整引用关系,当做 backtrace dump 下来,count 是当前节点的总大小 (包括所有子节点) ,也就是火焰图上的宽度;

4. 从 bt 文件生成 svg,这一步是 brendangregg 的 FlameGraph 标准工具链。

Part.5--使用方式

这个工具是基于 Go 官方的 viewcore 改进来的。不过,鉴于 Go 官方不那么热心维护 viewcore 了,MOSN 社区先 fork 了一份,搞了个 mosn 分支,作为 MOSN 社区维护的主分支。

之前也给 Go 官方 debug 提交了好几个 bugfix,等后面有空,我们再去提交这个 feature。

所以,使用方式如下:

# 编译 mosn 维护的 viewcore
git clone git@github.com:mosn/debug.git
cd debug/cmd/viewcore
go build .

# 假设已经有了一个 core 文件(CORE-FILE)
# 以及对应的可执行程序文件(BIN-FILE)
viewcore CORE-FILE --exe BIN-FILE objref ref.bt

# 下载 FlameGraph 工具
git clone git@github.com:brendangregg/FlameGraph.git
../FlameGraph/stackcollapse-stap.pl ref.bt | ../FlameGraph/flamegraph.pl> ref.svg

# 浏览器打开 ref.svg 即可看到火焰图

如果使用碰到问题,可以随时联系我们或提交 issue(https://github.com/mosn/mosn/issues)。

当然,倘若你成功定位了某个问题,也欢迎与我们共同分享,Let's have fun together!

MOSN 用户钉钉群:33547952

相关链接

[1]《Go 语言 pprof heap profile 实现机制》:https://uncledou.site/2022/go-pprof-heap/

[2]OpenResty 官方博客:https://blog.openresty.com.cn/cn/openresty-xray-case-yundun/

[3]《Go 语言,如何做逆向类型推导》:https://uncledou.site/2022/go-type-derivation/

了解更多…

MOSN Star 一下✨: https://github.com/mosn/mosn

插播一条好消息!🤩

对 Go 语言开发感兴趣的小伙伴们,欢迎大家参与到近期正热的 GoCity 项目体验

点击此处查看演示视频,快速入门吧🥳

本周推荐阅读

MOSN 反向通道详解

Go 原生插件使用问题全解析

Go 代码城市上云--KusionStack 实践

MOSN 文档使用指南

相关文章
|
7月前
|
存储 Go iOS开发
掌握Go语言:探索Go语言指针,解锁高效内存操作与动态数据结构的奥秘(19)
掌握Go语言:探索Go语言指针,解锁高效内存操作与动态数据结构的奥秘(19)
|
7月前
|
存储 缓存 安全
Go语言内存模型深度解析
【2月更文挑战第16天】Go语言以其简洁的语法、强大的并发编程能力和高效的内存管理而备受开发者青睐。本文将对Go语言的内存模型进行深度解析,探讨其内存布局、内存分配与回收机制以及内存安全等方面的内容,帮助读者更好地理解和应用Go语言的内存管理特性。
|
1月前
|
Java 编译器 测试技术
go语言避免不必要的内存分配
【10月更文挑战第18天】
43 1
|
1月前
|
存储 算法 Java
Go语言的内存管理机制
【10月更文挑战第25天】Go语言的内存管理机制
26 2
|
4月前
|
存储 安全 编译器
Go 内存分布
该文章深入分析了Go语言中值的内存分布方式,特别是那些分布在多个内存块上的类型,如切片、映射、通道、函数、接口和字符串,并讨论了这些类型的内部结构和赋值时的行为,同时指出了“引用类型”这一术语在Go中的使用可能会引起的误解。
54 5
Go 内存分布
|
7月前
|
算法 Java Go
Go vs Java:内存管理与垃圾回收机制对比
对比了Go和Java的内存管理与垃圾回收机制。Java依赖JVM自动管理内存,使用堆栈内存并采用多种垃圾回收算法,如标记-清除和分代收集。Go则提供更多的手动控制,内存分配与释放由分配器和垃圾回收器协同完成,使用三色标记算法并发回收。示例展示了Java中对象自动创建和销毁,而Go中开发者需注意内存泄漏。选择语言应根据项目需求和技术栈来决定。
|
4月前
|
NoSQL Java 测试技术
Golang内存分析工具gctrace和pprof实战
文章详细介绍了Golang的两个内存分析工具gctrace和pprof的使用方法,通过实例分析展示了如何通过gctrace跟踪GC的不同阶段耗时与内存量对比,以及如何使用pprof进行内存分析和调优。
107 0
Golang内存分析工具gctrace和pprof实战
|
4月前
|
存储 安全 Go
Go 中的指针:了解内存引用
Go 中的指针:了解内存引用
|
4月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
4月前
|
缓存 编解码 测试技术
使用Go实现健壮的内存型缓存
使用Go实现健壮的内存型缓存
72 2