python objgraph
## Python objgraph一个追踪内存对象的特别工具说到objgraph得先聊聊一个很多人写Python时都会遇到的场景。你在开发一个长时间运行的脚本内存一直往上涨但你明明该释放的都释放了。这时候你可能会想能不能像看显微镜一样看清内存里到底有哪些对象谁在引用谁。objgraph就是干这个的。它到底是什么直观地说objgraph是一个能让你“看到”Python对象之间关系的工具。它能画出对象引用图而且不需要你改代码、加装饰器也不需要侵入式的监控。它更像一个快照工具在你程序运行的某个时刻帮你拍一张内存中对象的照片然后把这些关系画成图给你看。和大多数人的直觉不同objgraph并不是一个内存分析器比如memory_profiler那种它不追踪内存分配的时间线。它关注的是对象的引用拓扑结构。举个例子你写了一个函数生成了一些对象但那些对象莫名其妙没有被回收这个时候用objgraph可以很清楚地看到是哪个变量还在引用着它们。它能做什么我比较常用它的几个场景第一个是用来排查内存泄漏。不是那种明显的大泄漏而是那种对象一直在累积的情况。比如说你有一个缓存理论上应该用LRU淘汰旧数据但实际代码写错了缓存越涨越大。用objgraph可以看看缓存里到底存活了多少对象这些对象都被什么引用着。第二个是理解对象生命周期。有时候你觉得某个对象应该被垃圾回收了但实际情况可能和你想象的不一样。比如函数内部的循环引用或者闭包里不小心抓住了外部变量。用objgraph可以验证你的代码逻辑是否真的和你预期一致。第三个是优化数据结构。当你有一棵很大的树或者图结构想知道每个节点有没有被意外地共享引用导致修改一个节点影响了很多地方这时候也能用objgraph。还有一个用处平常不太被人提——它很适合给刚学Python的人演示对象引用和垃圾回收的概念。画出对象图比讲几十遍理论都直观。怎么使用安装很简单pip install objgraph就行。但要注意它依赖graphviz来画图所以你得额外安装graphviz的绘图引擎。最常用的函数是show_refs和show_backrefs。show_refs是看一个对象引用了哪些其他对象show_backrefs是看有哪些对象引用了它。拿个简单的例子来说importobjgraphclassMyObj:passaMyObj()bMyObj()cMyObj()a.refb b.refc c.refa# 形成了一个环objgraph.show_refs([a],filenamerefs.png)这会画出一个图显示a引用了bb引用了cc又引用了a。对于找出循环引用这种图非常直观。还有一个很实用的函数是count可以统计某种类型的对象有多少个。比如你想知道当前内存里有多少个字符串对象objgraph.count(str)最常用的是show_growth它能持续输出对象数量和变化。我经常在服务运行一段时间后隔几秒调用一次看看哪些类型的对象在持续增长。importtimeimportobjgraphwhileTrue:objgraph.show_growth()time.sleep(5)如果某个类型比如list或者dict的对象数量一直在增加又没有下降趋势大概率就是有泄漏。还有一个隐藏技巧是自己定义过滤器。比如你想只看自己写的类不看Python内置的类型可以给show_refs传一个自定义的过滤器函数。最佳实践用objgraph的时候有几点大概需要注意一下。第一别在生产环境一直开着。objgraph本身会创建对象影响性能。我一般只在复现问题的时候在代码里插入调用的代码截图完再删掉。或者开一个独立的调试端点需要的时候再触发。第二它更适合查“为什么这个对象没有被回收”这样的具体问题而不是“为什么内存涨了”这种笼统的问题。建议先用tracemalloc或者memory_profiler定位到大概的内存增长点再用objgraph深入检查那个点的对象引用关系。就像先用金属探测器找出炸弹的大概位置再用铲子慢慢挖。第三有时候画出的图会非常巨大。如果你的对象图特别复杂建议先用filter参数限制一下范围。比如只关注某个自定义类或者只关注内置类型的对象。第四它更适合查引用泄漏而不是内存碎片或者原生扩展泄漏。如果你的问题出在C扩展或者底层内存分配上objgraph帮不了你太多。第五有一点没人提但我觉得挺有用——可以把它和gc模块配合。先用gc.get_referrers找出哪些对象引用了目标对象再用objgraph可视化这样能定位得又快又准。和同类技术对比和objgraph比较接近的是pympler。pympler也能统计对象数量和大小但它不擅长画引用图。pympler的asizeof可以直接告诉你一个对象实际占多少内存包括它引用的其他对象这点objgraph做不到。反过来objgraph的引用关系可视化pympler也比不了。所以实际问题中我经常一起用这两个工具。memory_profiler走的是另一个路子——它监控的是进程整体的内存使用随时间的变化而不是对象级别的快照。它能告诉你哪一行代码分了多少内存但看不到具体对象之间的引用关系。用记忆练习来比喻的话memory_profiler像是在看一个人的体重变化曲线而objgraph像是在看这个人身体的X光片。还有gc模块自带的功能比如gc.get_objects和gc.get_referrers。它们和objgraph一样都能访问对象但gc模块返回的是文字信息没有可视化调试复杂的引用链会比较低效。objgraph本质上是在gc模块的基础上加了画图的能力。另外值得提一下的是tracemallocPython 3.4之后自带的模块。它追踪的是内存分配的回溯能告诉你哪些代码路径分配了内存。但它不关心对象之间的引用关系和objgraph的侧重点不太一样。如果是在调试内存泄漏的问题我觉得比较好的流程是先用tracemalloc看哪些分配路径在增长缩小范围到几个关键函数再用memory_profiler或者内置的resource模块看内存增长的时间模式定位到具体的对象后用objgraph或者pympler深入分析引用关系。这三个工具各有所长搭配着用效果更好。objgraph的局限性主要在于它依赖graphviz有时候画图的配置比较麻烦。而且如果对象图非常复杂生成图片会很慢甚至可能卡住。另外它不统计内存大小只能看对象个数和引用关系对于内存泄漏排查来说有时候知道对象多大比知道多少个更有用。不过话说回来objgraph有一个非常独特的价值——它是极少能让你“看到”对象引用关系的工具。很多时候问题光靠猜是猜不出来的看一张图比读一千行代码要直接得多。