全文拷贝自:http://blog.csdn.net/BaishanCloud/article/details/76422782

问题定位过程解读


gdb-python:搞清楚python程序在做什么 
首先确定python在做什么,是否有大内存消耗任务正在运行,或出现死锁等异常行为。

从gdb-7开始,gdb支持用python实现gdb扩展,可以像调试c程序一样,用gdb对python程序检查线程、调用栈等;且可同时打印python代码和内部c代码的调用栈。

这对于定位是python代码问题还是其底层c代码问题,有很大帮助。

  • 准备gdb 
    首先安装python的debuginfo:
 # debuginfo-install python-2.7.5-39.el7_2.x86_64

如果缺少debuginfo,当运行后续步骤时,gdb会提示,按提示安装完成即可:

Missing separate debuginfos, use: debuginfo-install python-2.7.5-39.el7_2.x86_64
  • 接入gdb 
    可直接用gdb attach到1个python进程,查看其运行状态:
# gdb python 11122

attach之后进入gdb,基本检查步骤如下:

  • 查看线程
(gdb) info threads
Id Target Id Frame
206 Thread 0x7febdbfe3700 (LWP 124916) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
205 Thread 0x7febdb7e2700 (LWP 124917) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
204 Thread 0x7febdafe1700 (LWP 124918) "python2" 0x00007febe9b75413 in select () at ../sysdeps/unix/syscall-template.S:81
203 Thread 0x7febda7e0700 (LWP 124919) "python2" 0x00007febe9b7369d in poll () at ../sysdeps/unix/syscall-template.S:81

一般加锁、死锁情况存在时,会有线程卡在xx_wait等函数上。

之前用该方法定位了1个python-logging模块引起的死锁问题: 
在多线程进程中运行fork,导致logging的锁被锁住后fork到新进程、但解锁线程没有fork到新进程而导致死锁。。

  • 查看调用栈 
    如果发现某线程有问题,切换到此线程上,查看调用栈确定具体执行步骤,使用bt 命令:
(gdb) bt
#16 0x00007febea8500bd in PyEval_EvalCodeEx (co=<optimized out>, globals=<optimized out>, locals=locals@entry=0x0, args=<optimized out>, argcount=argcount@entry=1, kws=0x38aa668, kwcount=2, defs=0x3282a88, defcount=2, closure=closure@entry=0x0) at /usr/src/debug/Python-2.7.5/Python/ceval.c:3330 ... #19 PyEval_EvalFrameEx (f=f@entry=Frame 0x38aa4d0, for file t.py, line 647, in run (part_num=2, consumer=<...

bt 命令不仅可以看到c的调用栈,还会显示python源码的调用栈。 上图中,frame-16是c的调用栈,frame-19显示python源代码的所在行。

如果只查看python代码的调用栈,则使用py-bt命令:

(gdb) py-bt
#1 <built-in method poll of select.epoll object at remote 0x7febeacc5930>
#3 Frame 0x3952450, for file /usr/lib64/python2.7/site-packages/twisted/internet/epollreactor.py, line 379, in doPoll(self=<... l = self._poller.poll(timeout, len(self._selectables))
#7 Frame 0x39502a0, for file /usr/lib64/python2.7/site-packages/twisted/internet/base.py, line 1204, in mainLoop (self=<...

py-bt显示python源码的调用栈、调用参数以及所在行的代码。

  • coredump 
    如果要进行长时间跟踪,最好 coredump下python程序的全部进程信息,之后再分析core文件,避免影响正在运行的程序。
(gdb) generate-core-file

这条命令将当前gdb attach的程序dump到其运行目录,命名为core.,然后使用gdb 加载该core文件,进行打印堆栈、查看变量等分析,无需attach到正在运行的程序:

# gdb python core.<pid>
  • 其他命令 
    其他命令可以在gdb输入py 查看,与gdb的命令对应,例如:
(gdb) py
py-bt py-list py-print python
py-down py-locals py-up python-interactive

-py-up、py-down 可移动到python调用栈的上一个或下一个frame; 
-py-locals 用来打印局部变量…… 
gdb中也可用help命令查看帮助:

(gdb) help py-print
Look up the given python variable name, and print it

在这次追踪过程中,用gdb-python排除了程序逻辑问题。接下来继续追踪内存泄漏问题。

pyrasite: 连接进入python程序

pyrasite可以直接连上一个正在运行的python程序,打开一个类似ipython的交互终端来运行命令、检查程序状态。

这为调试提供了极大的方便。 
安装:

# pip install pyrasite
... # pip show pyrasite
Name: pyrasite
Version: 2.0
Summary: Inject code into a running Python process
Home-page: http://pyrasite.com
Author: Luke Macken
...

连接到有问题的python程序,开始收集信息:

pyrasite-shell <pid>
>>>

接下来就可以在进程里调用任意python代码,查看进程状态。

psutil 查看python进程状态

pip install psutil

首先查看python进程占用的系统内存RSS:

pyrasite-shell 11122
>>> import psutil, os
>>> psutil.Process(os.getpid()).memory_info().rss 29095232

基本与ps命令显示结果一致:

rss the real memory (resident set) size of the process (in 1024 byte units)

guppy 获取内存使用的各种对象占用情况 
guppy 可以打印各种对象所占空间大小,如果python进程中有未释放的对象,造成内存占用升高,可通过guppy查看。

同样,以下步骤是通过pyrasite-shell,attach到目标进程后操作的。
# pip install guppy
from guppy import hpy
h = hpy() h.heap()
# Partition of a set of 48477 objects. Total size = 3265516 bytes.
# Index Count % Size % Cumulative % Kind (class / dict of class)
# 0 25773 53 1612820 49 1612820 49 str
# 1 11699 24 483960 15 2096780 64 tuple
# 2 174 0 241584 7 2338364 72 dict of module
# 3 3478 7 222592 7 2560956 78 types.CodeType
# 4 3296 7 184576 6 2745532 84 function
# 5 401 1 175112 5 2920644 89 dict of class
# 6 108 0 81888 3 3002532 92 dict (no owner)
# 7 114 0 79632 2 3082164 94 dict of type
# 8 117 0 51336 2 3133500 96 type
# 9 667 1 24012 1 3157512 97 __builtin__.wrapper_descriptor
# <76 more rows. Type e.g. '_.more' to view.>
h.iso(1,[],{})
# Partition of a set of 3 objects. Total size = 176 bytes.
# Index Count % Size % Cumulative % Kind (class / dict of class)
# 0 1 33 136 77 136 77 dict (no owner)
# 1 1 33 28 16 164 93 list
# 2 1 33 12 7 176 100 int

通过以上步骤可排除python进程中存在未释放的对象的可能。

无法回收的对象

python本身带有垃圾回收,但同时满足以下2个条件时,python程序中个别对象则无法被回收(uncollectable object) : 
- 循环引用 
- 循环引用链上某对象定义了del方法

官方解释是:循环引用的一组对象被gc模块识别为可回收,但需先调用每个对象上的del才可被回收。当用户自定义了del的对象,gc系统无法判断应先调用环上的哪个del,因此无法回收这类对象。

不能回收的python对象会持续占据内存,因此,我们推测有不能被回收的对象导致了内存持续升高。

最终确定不是由这种问题引起的内存无法释放。不能回收的对象仍可通过gc.get_objects() 列出,并会在gc.collect()调用后加入gc.garbage的list里。但目前尚未发现这类对象的存在。 
查找uncollectable的对象:

pyrasite-shell 11122
>>> import gc
>>> gc.collect() # first run gc, find out uncollectable object and put them in gc.garbage
# output number of object collected
>>> gc.garbage # print all uncollectable objects
[] # empty

如果打印出任何不能回收的对象,则需进一步查找,确定循环引用链上哪个对象包含del方法。

下面应用1个例子来演示如何生成不能回收的对象:

from __future__ import print_function
import gc '''
This snippet shows how to create a uncollectible object:
It is an object in a cycle reference chain, in which there is an object
with __del__ defined.
The simpliest is an object that refers to itself and with a __del__ defined. > python uncollectible.py ======= collectible object ======= *** init, nr of referrers: 4
garbage: []
created: collectible: <__main__.One object at 0x102c01090>
nr of referrers: 5
delete:
*** __del__ called
*** after gc, nr of referrers: 4
garbage: [] ======= uncollectible object ======= *** init, nr of referrers: 4
garbage: []
created: uncollectible: <__main__.One object at 0x102c01110>
nr of referrers: 5
delete:
*** after gc, nr of referrers: 5
garbage: [<__main__.One object at 0x102c01110>] ''' def dd(*msg):
for m in msg:
print(m, end='')
print() class One(object): def __init__(self, collectible):
if collectible:
self.typ = 'collectible'
else:
self.typ = 'uncollectible' # Make a reference to it self, to form a reference cycle.
# A reference cycle with __del__, makes it uncollectible.
self.me = self def __del__(self):
dd('*** __del__ called') def test_it(collectible): dd()
dd('======= ', ('collectible' if collectible else 'uncollectible'), ' object =======')
dd() gc.collect()
dd('*** init, nr of referrers: ', len(gc.get_referrers(One)))
dd(' garbage: ', gc.garbage) one = One(collectible)
dd(' created: ', one.typ, ': ', one)
dd(' nr of referrers: ', len(gc.get_referrers(One))) dd(' delete:')
del one gc.collect() dd('*** after gc, nr of referrers: ', len(gc.get_referrers(One)))
dd(' garbage: ', gc.garbage) if __name__ == "__main__":
test_it(collectible=True)
test_it(collectible=False)

上面这段代码创建了2个对象:1个可回收、1个不可回收,它们都定义了del方法,唯一区别在于是否引用了自己(从而构成了引用环)。

如果在这个步骤发现了循环引用,则需进一步查出具体哪些引用关系造成了循环,进而破坏循环引用,最终让对象可回收。

objgraph 查找循环引用

# pip install objgraph
pyrasite-shell 11122
>>> import objgraph
>>> objgraph.show_refs([an_object], filename='sample-graph.png')

以上例子中,将在本地生成一个图片,描述可以由an_object引用到的关系图:

在这一步我们仍未找到不能回收的对象,排除一切原因后我们推测libc的malloc实现问题。使用tcmalloc替代libc默认的malloc后问题最终得到修复。

【python】内存调试的更多相关文章

  1. 记一次调试python内存泄露的问题

    转载:http://www.jianshu.com/p/2d06a1a01cc3 这两天由于公司需要, 自己编写了一个用于接收dicom文件(医学图像文件)的server. 经过各种coding-de ...

  2. 使用gc、objgraph干掉python内存泄露与循环引用!

    Python使用引用计数和垃圾回收来做内存管理,前面也写过一遍文章<Python内存优化>,介绍了在python中,如何profile内存使用情况,并做出相应的优化.本文介绍两个更致命的问 ...

  3. [转] 使用gc && objgraph 优化python内存

    转自https://www.cnblogs.com/xybaby/p/7491656.html 使用gc.objgraph干掉python内存泄露与循环引用!   目录 一分钟版本 python内存管 ...

  4. Python内存管理机制及优化简析(转载)

    from:http://kkpattern.github.io/2015/06/20/python-memory-optimization-zh.html 准备工作 为了方便解释Python的内存管理 ...

  5. 排查python内存泄露中几个工具的使用

    本文主要介绍3个工具:pdb,objgraph,以及pympler. 1.pdb pdb是专门用于python代码调试,模仿gdb. 使用pdb可以查看堆栈,打印变量等. 这里介绍的是命令行下的pdb ...

  6. 关于排查python内存泄露的简单总结

    这次的内存泄露问题是发生在多线程场景下的. 各种工具都试过了,gc,objgraph, pdb,pympler等,仍然没有找到问题所在. pdb感觉用起来很方便,可以调试代码,对原来的代码无侵入性. ...

  7. python内存泄露memory leak排查记录

    问题描述 A服务,是一个检测MGR集群主节点是否发生变化的服务,使用python语言实现的. 针对每个集群,主线程会创建一个子线程,并由子线程去检测.子线程会频繁的创建和销毁. 上线以后,由于经常会有 ...

  8. 珍惜每一滴水(kbmmw 中的内存调试)

    作为一个服务器端的应用,最基本的要求就是稳定,当然要做一个稳定的服务器端,需要涉及到很多方面, 内存泄露就是稳定的一个致命杀手,因为服务器的物理内存是有限的,即使一个功能有很小的内存泄露,经过 长时间 ...

  9. python基础——调试

    python基础——调试 程序能一次写完并正常运行的概率很小,基本不超过1%.总会有各种各样的bug需要修正.有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是 ...

  10. 在ubunut下使用pycharm和eclipse进行python远程调试

    我比较喜欢Pycharm,因为这个是JetBrains公司出的python IDE工具,该公司下的java IDE工具--IDEA,无论从界面还是操作上都甩eclipse几条街,但项目组里有些人使用e ...

随机推荐

  1. Lr-代理录制

    哈哈,第一讲,决定分享一下代理的一些知识,是我学习的总结,可能会有错误,欢迎大家指正. 问题一:代理录制是为了解决什么问题或者说为什么么要使用代理呢? 这是因为lr只能使用IE浏览器进行录制,如果想使 ...

  2. 类中定义的方法,self参数

    class a(): def __init__(self): self.aa = 5 def test(): print "haha" a.test() self指的是对象本身,而 ...

  3. Eclipse同时显示两个编辑窗口

    同时打开两个编辑窗口,点住一个窗口,拖到编辑窗口的最下面时或者最右面,会出现两个两个编辑窗口的轮廓,松开即可!

  4. C/C++中 # 的神奇作用:把宏参数字符串化/贴合宏参数

    一.一般用法   我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起. #define STR(s) #s #define CONS(a,b) int(a##e##b) printf(ST ...

  5. How to Train Triplet Networks with 100K Identities?

    1. 为什么介绍此文? Triplet net 改进工作之一,主要思想是在大数据集(人脸识别)上的困难样本挖掘.人脸识别工作对于图像对匹配而言很有借鉴意义,共性是特征的提取和样本数据的挖掘. Trip ...

  6. webstorm的快捷键总结

    ctrl+shift+f 可以在项目和模块中的文件中查找特定字符串 shift键连续敲一下,可以在项目和模块中查找特定的文件 ctrl+d  复制当前行并粘贴到下一行 ctrl+shift+上下方向键 ...

  7. Linux网络底层收发探究【转】

    转自:https://blog.csdn.net/davion_zhang/article/details/51536807 本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...

  8. python3+selenium框架设计01-Page Object

    页面对象模型Page Object Modal是一种脚本设计模型,将页面元素,业务操作分割,当实际页面发生变化的时候,只需要修改页面元素文件,业务操作不需要修改. 具体实现需要先写一个页面公共类,里面 ...

  9. NodeJS基础教程

    关于 本书致力于教会你如何用Node.js来开发应用,过程中会传授你所有所需的“高级”JavaScript知识.本书绝不是一本“Hello World”的教程. 状态 你正在阅读的已经是本书的最终版. ...

  10. vc++基础班[25]---系统信息的获取

    --------------------------------------------------------------------------- VC 驿站 WwW.CcTry.CoM 多抽出一 ...