【python】内存调试
全文拷贝自: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 11122attach之后进入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】内存调试的更多相关文章
- 记一次调试python内存泄露的问题
		转载:http://www.jianshu.com/p/2d06a1a01cc3 这两天由于公司需要, 自己编写了一个用于接收dicom文件(医学图像文件)的server. 经过各种coding-de ... 
- 使用gc、objgraph干掉python内存泄露与循环引用!
		Python使用引用计数和垃圾回收来做内存管理,前面也写过一遍文章<Python内存优化>,介绍了在python中,如何profile内存使用情况,并做出相应的优化.本文介绍两个更致命的问 ... 
- [转] 使用gc && objgraph 优化python内存
		转自https://www.cnblogs.com/xybaby/p/7491656.html 使用gc.objgraph干掉python内存泄露与循环引用! 目录 一分钟版本 python内存管 ... 
- Python内存管理机制及优化简析(转载)
		from:http://kkpattern.github.io/2015/06/20/python-memory-optimization-zh.html 准备工作 为了方便解释Python的内存管理 ... 
- 排查python内存泄露中几个工具的使用
		本文主要介绍3个工具:pdb,objgraph,以及pympler. 1.pdb pdb是专门用于python代码调试,模仿gdb. 使用pdb可以查看堆栈,打印变量等. 这里介绍的是命令行下的pdb ... 
- 关于排查python内存泄露的简单总结
		这次的内存泄露问题是发生在多线程场景下的. 各种工具都试过了,gc,objgraph, pdb,pympler等,仍然没有找到问题所在. pdb感觉用起来很方便,可以调试代码,对原来的代码无侵入性. ... 
- python内存泄露memory leak排查记录
		问题描述 A服务,是一个检测MGR集群主节点是否发生变化的服务,使用python语言实现的. 针对每个集群,主线程会创建一个子线程,并由子线程去检测.子线程会频繁的创建和销毁. 上线以后,由于经常会有 ... 
- 珍惜每一滴水(kbmmw 中的内存调试)
		作为一个服务器端的应用,最基本的要求就是稳定,当然要做一个稳定的服务器端,需要涉及到很多方面, 内存泄露就是稳定的一个致命杀手,因为服务器的物理内存是有限的,即使一个功能有很小的内存泄露,经过 长时间 ... 
- python基础——调试
		python基础——调试 程序能一次写完并正常运行的概率很小,基本不超过1%.总会有各种各样的bug需要修正.有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是 ... 
- 在ubunut下使用pycharm和eclipse进行python远程调试
		我比较喜欢Pycharm,因为这个是JetBrains公司出的python IDE工具,该公司下的java IDE工具--IDEA,无论从界面还是操作上都甩eclipse几条街,但项目组里有些人使用e ... 
随机推荐
- mysql 文件操作 表
			一 : 访问库 use db1 查询当前表所在文件夹 : select database(); 增: create table t1(id int, name chat(10)); 查询: ... 
- js原生事件
			js原生事件封装 // 事件处理对象 var EventUtil = { // 添加事件监听 add: function(element, type, callback){ if(element.ad ... 
- 【.net ajax显示后台返回值】
			1..net ajax显示后台返回值 <script> $(document).ready(function () { $("#btn&quo ... 
- java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException or 程序包 javax.servlet 不存在
			遇到下面这个问题 程序包 javax.servlet 不存在 或者 java.util.concurrent.ExecutionException: org.apache.catalina.Lifec ... 
- compileSdkVersion,minSdkVersion 和 targetSdkVersion
			compileSdkVersion(Eclipse中叫做build target) 1.在eclipse中位于项目根目录中的project.properties文件中 2.在studio中位于项目中的 ... 
- Network Architecture Search Survey
- Nginx在局域网中使用ip_hash负载均衡策略,访问全部分发到同一个后台服务器
			Nginx的ip_hash算法都将一个ip地址的前三段作为hash的关键字 
- python3+selenium入门02-操作火狐浏览器
			使用selenium打开浏览器的时候,需要先去selenium官网下载一个对应浏览器的的driver插件.selenium下载.官网在国外可能打不开.或者可以从我网盘下载,但可能不是最新的网盘链接.将 ... 
- requests库入门06-post请求
			示例相应的接口文档:GitHub邮箱接口文档 先登录GitHub,然后右上角用户下拉框中选择settings,然后选Emails.可以看到当前账户设置的邮箱情况 再看添加邮箱接口的文档描述,可以通过一 ... 
- 用UDP实现聊天功能
			// SK003Dlg.cpp : implementation file // #include "stdafx.h" #include "SK003.h" ... 
