一、表现

运行环境:

# uname –a

Linux ** 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

# python2 –version

# cat /etc/*-release

CentOS Linux release 7.2.1511 (Core)

python进程在大量请求的处理过程中,内存持续升高。负载压力下降之后,内存并未下降。

python进程在大量请求的处理过程中,内存持续升高。负载压力下降之后,内存并未下降。

# ps aux | grep python2

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

root     124910 10.2  0.8 5232084 290952 ?      Sl   Mar17 220:37 python2 offline.py restart

#                                                        ~~~~~~

#                                                        290M 内存占用

二、解决方法

三个步骤确定问题所在:

确认当时程序是否有异常行为;

排除行为异常后,查看python内存使用情况,确认所有相关对象的回收情况;

排除垃圾回收等python内部内存泄漏问题后,则可以定位到libc的malloc实现问题;

确定后可直接替换malloc模块为tcmalloc:、

LD_PRELOAD=”/usr/lib64/libtcmalloc.so” python x.py

三、问题定位过程解读

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” 0x00007febe9b75413in 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 inpoll () 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=, globals=, locals=locals@entry=0x0, args=,

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

#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.

其他命令

其他命令可以在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

>>>

接下来就可以在进程里调用任意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个例子来演示如何生成不能回收的对象:

# 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’ifcollectible 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后问题最终得到修复。

原创文章,作者:白山码路长,如若转载,请注明出处:http://blog.baishan.com/technology-applications/python-jinchengneicunzengchangjiejuefangan

python内存持续增长_Python 进程内存增长解决方案相关推荐

  1. python 内存溢出_Python之内存泄漏和内存溢出

    一.内存泄漏 像Java程序一样,虽然Python本身也有垃圾回收的功能,但是同样也会产生内存泄漏的问题. 对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是 ...

  2. python启动方法_python进程开启的两种方式

    一.进程 1.1.方式一 from multiprocessing import Process import time #方式一 def task(name): print(f"my na ...

  3. linux进程实际内存大小,Linux进程内存用量分析之堆内存篇

    本文将介绍几种内存泄漏检测工具,并通过实际例子介绍一种分析堆内存占用量的工具和方法,帮助定位内存膨胀问题.背景 进程的内存管理是每一个开发者必须要考虑的问题,对于C++程序进程来说,出现问题很多情况下 ...

  4. linux查看所有进程占用内存命令,LINUX进程内存占用查看命令

    (1)top使命 代码如下 $ top -u oracle 1.top命令 代码如下 top -d 1 -p pid [,pid ...] //设置为delay 1s,默认是delay 3s 如果想根 ...

  5. linux rss 内存分析,Linux进程内存分析pmap命令

    名称: pmap - report memory map of a process(查看进程的内存映像信息) 用法 pmap [ -x | -d ] [ -q ] pids... pmap -V 选项 ...

  6. linux进程的内存空间,Linux进程内存统计

    一. 进程内存统计 cat /proc/[pid]/status 通过/proc/[pid]/status可以查看进程的内存使用情况,包括虚拟内存大小(VmSize),物理内存大小(VmRSS),数据 ...

  7. python slice是共享内存吗_python共享内存实现进程通信

    1.概述 共享内存可以说是最有用的进程间通信方式.两个不同的进程共享内存的意思是:同一块物理内存被映射到两个进程的各自的进程地址空间.一个进程可以及时看到另一个进程对共享内存的更新,反之亦然.采用共享 ...

  8. python怎么清理电脑进程内存_Python如何释放内存?

    功能说明 Python使用引用计数.分代算法回收垃圾,引用计数操作方法: 对象被引用一次,其计数器+1 对象被del,其计数器-1 对象的引用计数为0时候会被回收 python的魔法方法__del__ ...

  9. 限制python内存上限_Python限制内存和CPU使用量的方法(Unix系统适用)

    问题 你想对在Unix系统上面运行的程序设置内存或CPU的使用限制. 解决方案 resource 模块能同时执行这两个任务.例如,要限制CPU时间,可以像下面这样做: import signal im ...

最新文章

  1. 关于object references an unsaved transient instance - save the transient instance before flushing的解决方法
  2. 1076 Forwards on Weibo
  3. Spring Cloud架构的各个组件的原理分析
  4. NIS、NFS 與 Autofs 整合應用
  5. angular artDialog未及时更新
  6. jsp java 分离,java与jsp页面的字符串拼接和拆分
  7. SAP Spartacus content page url的硬编码列表 - Page Label
  8. 租号显示服务器爆满怎么办,租号器环境异常怎么解决
  9. C++ 控制结构和函数(一) —— 控制结构
  10. 开红数显示服务器为空,网维大师常见问题:图标空白或红号问号
  11. 如何看待现在一些年轻人秉持「简单是福、平淡是真」的随遇而安的生活态度?...
  12. HL7协议介绍(一)
  13. 日志易使用系列三:仪表盘的创建与使用
  14. android面板驱动的使用方法,高通平台Android 驱动层LCD显示屏驱动移植说明和相关工具...
  15. 解决eclipse安装Darkest Dark Theme报错
  16. 1号线彭埠站故障已经查明 是积水导致转辙机信号不良
  17. Renesas:配置中断的常规流程
  18. Android端控制LED屏
  19. 苹果cms10的一次尝试发现了苹果cms10被挂马极有可能是苹果cms作者故意的js漏洞或后门导致
  20. OC和swift混合工程更新库时报:target has transitive dependencies that include statically linked binaries

热门文章

  1. AutoCAD二次开发之删除图层及图层上所有的实体
  2. 组原4_指令偏移寻址
  3. Cisco ❀ VRF(虚拟路由转发表)
  4. 阿里云大数据工程师ACP认证例题
  5. MD文本编辑工具推荐-matktext
  6. Flink(60):Flink中通用ModelUtil工具类
  7. 数学建模竞赛解题思路
  8. 使用 html2canvas 生成图片
  9. 2016计算机二级ps试题,2016下半年计算机二级ps试题及答案
  10. win10提示无法通过电话激活怎么办?