精华内容
下载资源
问答
  • Python内存泄露的诊断

    千次阅读 2013-09-12 19:43:58
    内存泄露的原因内存泄露的诊断思路内存泄露诊断用到的工具内存泄露诊断的步骤参考文档 对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是有了内存方面的问题。 在我...


    对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是有了内存方面的问题。 在我曾经的一个项目中,就出现了这种内存持续增长的情况,goolge 一下,发现 Tracing Python memory leaks 讲了一种诊断方式,并给出了实例。

    而我的案例与此文稍有不同,下面就结合我的案例,谈谈如何诊断这种“内存泄露”的问题。

    内存泄露的原因

    对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因:

    • 所用到的用C 语言开发的底层模块中出现了内存泄露。
    • 代码中用到了全局的 list、 dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完之后进行删除回收
    • 代码中有引用循环,python 垃圾处理机制无法进行回收

    内存泄露的诊断思路

    无论是哪种方式的内存泄露,最终表现的形式都是某些 python 对象在不停的增长;因此,首先是要找到这些异常的对象。

    内存泄露诊断用到的工具

    • gc 模块
    • objgraph模块
      • objgraph 是一个用于诊断内存问题的有用的工具

    内存泄露诊断的步骤

    • 在服务程序的循环逻辑中,选择出一个诊断点
    • 在诊断点,插入如下诊断语句
    Python代码
       1 import gc
       2 import objgraph
       3 gc.collect()
       4 ### 强制进行垃圾回收
       5 objgraph.show_most_common_types(limit=50)
       6 ### 打印出对象数目最多的 50 个类型信息 
     

    • 检查统计信息,找到异常对象。
    • 运行加入诊断语句的服务程序,并将打印到屏幕上的统计信息重定向到日志中。
    • 运行一段时间后,就可以来shell脚本来分析日志,看看哪些对象在不停的增长。
      • 以我的程序为例,我将日志记录到 log.txt 中,运行一段时间后,发现 tuple 和 list 类型的对象不停增长:
    Shell 代码
       1 # grep "^list " log.txt
       2 # grep "^tuple " log.txt 
     

    如果不停增长的对象,是一些非通用的类型(例如你自己实现的一个 class),那么问题就比较好定位,例如Tracing Python memory leaks中提到的案例。 

    而对 tuple 和 list 这类通用类型,要想知道对象到底是什么,泄露发生在哪里,还得想点办法。我采用了排查的方式。由于程序的模块化还不错,可以每次禁用一个模块(二分定位法),然后重新跑程序,重新检查日志,看看 tuple 和 list 是否仍然不停增长。这样,很快就能将故障定位到具体的模块中。 

    最后终于找到了原因,属于上面总结的第二种原因: 
    我的程序是一个多线程程序,多个线程作为生产者,一个线程作为消费者,通过将一个 tuple 对象送入异步队列进行通信。由于消费者的处理速度跟不上生产者的速度,又没有进行同步, 导致异步队列中的对象越来越多。

    参考文档

    展开全文
  • python 内存泄露的诊断

    千次阅读 2013-08-13 17:02:41
    一、内存泄露的原因 对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因: 1、 所用到的用 C 语言开发的底层模块中出现了内存泄露。 2、 代码中用到了全局的 ...
    对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是有了“内存泄露”


    一、内存泄露的原因


    对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因:


    1、 所用到的用 C 语言开发的底层模块中出现了内存泄露。


    2、 代码中用到了全局的 list、 dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完之后进行删除回收


    3、 代码中有“引用循环”, python 垃圾处理机制无法进行回收


    二、 内存泄露的诊断思路


    无论是哪种方式的内存泄露,最终表现的形式都是某些 python 对象在不停的增长;因此,首先是要找到这些异常的对象。


     
    三、 内存泄露的诊断步骤
     
    用到的工具: gc 模块和 objgraph


    objgraph 是一个用于诊断内存问题的有用的工具


    1、 在服务程序的循环逻辑中,选择出一个诊断点

    2、 在诊断点,插入如下诊断语句


    import gc  
    import objgraph  
    ### 强制进行垃圾回收  
    gc.collect()  
    ### 打印出对象数目最多的 50 个类型信息  
    objgraph.show_most_common_types(limit=50)  
     


    3、 检查统计信息,找到异常对象。


    运行加入诊断语句的服务程序,并将打印到屏幕上的统计信息重定向到日志中。


    运行一段时间后,就可以来分析日志,看看哪些对象在不停的增长。


    以我的有问题的程序为例,我将日志记录到 log.txt 中,运行一段时间后,发现 tuple 和 list 类型的对象不停增长:


    # grep "^list " log.txt


    # grep "^tuple " log.txt

     
    由于 tuple 和 list 是一种通用的类型,因此,那些不停增长的 tuple 和 list 到底是什么对象,还是无法得知。我们还得想点办法追踪下去。


    如果不停增长的对象,是一些非通用的类型(例如你自己实现的一个 class),那么问题就比较好定位了。


    回到我的这个异常程序,为了找出 tuple 和 list 到底是什么? 我采用了排查的方式。由于程序的模块化还不错,我可以每次禁用一个模块,然后重新跑程序,看看在这种情况下, tuple 和 list 是否仍然不停增长。这样,很快就能将故障定位到具体的模块中。
    展开全文
  • 1、内存泄露的原因 对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因: 所用到的用 C 语言开发的底层模块中出现了内存泄露。 代码中用到了全局的 list、 dict 或其它容器...
  • 手头一个系统上线后,节点机中agent应用在运行10天后,占用系统内存居然高达10GB以上,这显然是发生了严重内存泄露。   问题原因 python是动态语言,对用动态语言内存分析不是很容易,尝试了一下比较经典...

    问题现象

    手头一个系统上线后,节点机中agent应用在运行10天后,占用系统内存居然高达10GB以上,这显然是发生了严重内存泄露。

     

    问题原因

    python是动态语言,对用动态语言的内存分析不是很容易,尝试了一下比较经典的内存分析工具meliae,但是发现不是很好用。查了很多资料后,发现了https://github.com/pympler/pympler 这个工具,官方文档地址为:

    http://pythonhosted.org/Pympler/tutorials/muppy_tutorial.html

    具体的分析过程我就不在这里描述了,大家可以通过pympler的官方文档去尝试。

     

    在跟踪分析后,发现agent代码中的ProgressThread在一直增长,没有被释放。相关代码如下:

            progress_thread = ProgressThread(vid, segments, master_urls, self.hostname)
            progress_thread.setDaemon(True)
            progress_thread.start()
            self.progress_threads.append(progress_thread)

     可以看到,在类对象中,progress_threads这个列表,每次创建一个ProgressThread线程对象时,就会把对象插入到progress_threads列表中。

     

    python的垃圾回收机制中,会自动对引用计数为0的对象回收。这里每次创建的线程对象,都被插入到progress_threads中,这导致了即使线程运行完毕了,其引用计数一直为1,导致所有的线程对象无法被回收。

     

    问题解决

    知道了问题原因,就很好解决了。在线程退出的地方,加入如下一行代码

                if progress_thread.if_stop == True:
                    self.progress_threads.remove(progress_thread)

     让线程对象的引用计数为0,就解决了这个问题。

     

    展开全文
  • python的c/c++扩展内存泄露bug fix

    千次阅读 2011-12-28 14:09:08
    近日需要使用到某同学使用c++封装的python...py的封装最不好控制的就是引用计数了,很多时候不知道啥时候Py_INCREF,啥时候Py_DECREF,这也是为什么会存在 boost.python,pycxx的原因,诸如boost.python,pycxx的模块

    近日需要使用到某同学使用c++封装的python扩展模块,搭配好环境,写上代码,跑起来,top一下,发现内存一直在涨。。。

    于是找到源代码,大致瞅了下,发现貌似是一些引用计数的问题

    py的封装最不好控制的就是引用计数了,很多时候不知道啥时候Py_INCREF,啥时候Py_DECREF,这也是为什么会存在 boost.python,pycxx的原因,诸如boost.python,pycxx的模块把这些问题都给隐藏了

    ok,现在开始验证,在调用的py代码里面加上:

    print len(gc.get_objects())

    把程序run起来,发现数目一直在涨,目前可以确定:内存确实存在泄露。下一步,定位是哪里泄露。

    看代码,发现返回结果的时候,是打包成了一个新的result结构体,把这个结构体注释掉,仅仅调用segment函数,重新编译,仍然有泄露。

    看来问题不是在result结构体,研究segment的代码,发现里面的核心功能就是一个切词,把获取切词结果的函数注释掉,发现仍然有问题。

    于是怀疑是使用的切词的库有问题,如果找到所使用的版本,发现这个的四位版本list中确实有个版本fix了内存泄露的问题,那么问题很明显了:使用了一个内存泄露的c库。

    心中一阵狂喜,嗯嗯,问题应该可以解决了。

    于是更新所使用的c库,重新build出so,跑上py代码,top下观看内存,咦,怎么内存还是一直在涨而object不变?

    很奇怪,object数目不变,就表示没有内存泄露,但是内存却一直在涨。。。

    怀疑是代码写的不对,于是改用了两种方法来做:

    1,把写的c扩展的代码,改用类实现

    2,写个c的简单的封装,调用py的ctypes来使用这个so

    写完后验证了下,发现object仍然不变,但内存一直在涨。这说明:

    1,内存没泄露---否则object数目会一直增长

    2,很可能是py自己获取了内存后,就一直未释放(这里的未释放不是指不会释放该对象,而是指不会释放管理这个对象的object指针)

    google了下,发现很多人都曾验证过,py确实如此,比如 这里 ,另外,这里 也有一篇文章说为什么py不释放内存。

    于是想,好了,事情到此为止了,好歹有个交代了。于是准备放弃研究这个问题,把其归为py自己的内存管理的问题:py自己的pool中获取到内存就不会释放。

    原本以为事情就到此可以结束,中午吃饭和同事聊到这个问题,他说可以参考下py的iterator的方法呢?

    嗯,主意不错,我用iterator重新改写下这个c的扩展

    于是在 这里 发现了一个参考的用c写的iterator的实现,代码很好理解

    然后我打算实现下,开始修改代码,咦等等,代码中返回结构体的时候,有个PyString_FromString,会不是是这个函数有问题?

    跟进去看了下PyString_FromString的实现:

    PyObject *
    PyString_FromString(const char *str)
    {
        register size_t size;
        register PyStringObject *op;
    
        assert(str != NULL);
        size = strlen(str);
        if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {
            PyErr_SetString(PyExc_OverflowError,
                "string is too long for a Python string");
            return NULL;
        }
        if (size == 0 && (op = nullstring) != NULL) {
    #ifdef COUNT_ALLOCS
            null_strings++;
    #endif
            Py_INCREF(op);
            return (PyObject *)op;
        }
        if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
    #ifdef COUNT_ALLOCS
            one_strings++;
    #endif
            Py_INCREF(op);
            return (PyObject *)op;
        }
    
        /* Inline PyObject_NewVar */
        op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);
        if (op == NULL)
            return PyErr_NoMemory();
        PyObject_INIT_VAR(op, &PyString_Type, size);
        op->ob_shash = -1;
        op->ob_sstate = SSTATE_NOT_INTERNED;
        Py_MEMCPY(op->ob_sval, str, size+1);
        /* share short strings */
        if (size == 0) {
            PyObject *t = (PyObject *)op;
            PyString_InternInPlace(&t);
            op = (PyStringObject *)t;
            nullstring = op;
            Py_INCREF(op);
        } else if (size == 1) {
            PyObject *t = (PyObject *)op;
            PyString_InternInPlace(&t);
            op = (PyStringObject *)t;
            characters[*str & UCHAR_MAX] = op;
            Py_INCREF(op);
        }
        return (PyObject *) op;
    }

    啧啧,每个if分支里面都有处理引用计数,没有问题啊。。。

    那莫不是函数PyList_Append有问题?

    google了下,发现PyList_Append确实会增加待添加的这个临时对象的引用计数,于是看了下PyList_Append的实现代码:

    int
    PyList_Append(PyObject *op, PyObject *newitem)
    {
        if (PyList_Check(op) && (newitem != NULL))
            return app1((PyListObject *)op, newitem);
        PyErr_BadInternalCall();
        return -1;
    }
    核心的功能在app1函数中做的,于是跟进去看看:

    static int
    app1(PyListObject *self, PyObject *v)
    {
        Py_ssize_t n = PyList_GET_SIZE(self);
    
        assert (v != NULL);
        if (n == PY_SSIZE_T_MAX) {
            PyErr_SetString(PyExc_OverflowError,
                "cannot add more objects to list");
            return -1;
        }
    
        if (list_resize(self, n+1) == -1)
            return -1;
    
        Py_INCREF(v);
        PyList_SET_ITEM(self, n, v);
        return 0;
    }
    呃,原来在这里面有重新增加了临时对象的引用计数。。。重新增加了。。

    好了,问题明朗了,这个应该算是py的bug了么?appen的时候会重新增加引用计数,看看setitem的实现:

    int
    PyList_SetItem(register PyObject *op, register Py_ssize_t i,
                   register PyObject *newitem)
    {
        register PyObject *olditem;
        register PyObject **p;
        if (!PyList_Check(op)) {
            Py_XDECREF(newitem);
            PyErr_BadInternalCall();
            return -1;
        }
        if (i < 0 || i >= Py_SIZE(op)) {
            Py_XDECREF(newitem);
            PyErr_SetString(PyExc_IndexError,
                            "list assignment index out of range");
            return -1;
        }
        p = ((PyListObject *)op) -> ob_item + i;
        olditem = *p;
        *p = newitem;
        Py_XDECREF(olditem);
        return 0;
    }

    嗯,这个实现很好,没有增加对待插入的对象的引用计数

    修改代码,把pylist_append改用pylist_setitem,重新build出so,跑起代码,top看下内存,很好,内存不再增长。

    google了下,发现这个bug已经有人报过,可惜url被墙,google的快照倒是可以看到,摘录如下:

    PyList_Append increments the reference count of the item being appended to the list, and two of the places in the GDB Python code that use it don't take that into account.  The result is a memory leak.  The attached patch fixes that. 

    Tested by using a debug build of Python with reference counting enabled, and turning on dumping of final leftover objects. 

    代码的bug是在2011年9月6号commit进去的,我所使用的python版本的发布日期如下:

    Python 2.7.2 was released on June 11th, 2011.

    所以,嗯,是的,这个bug还未发布,好吧,那就先使用pylist_setitem吧。

    到此为止,问题全部解决。

    罗嗦几句:

    1,使用google code search到的代码,里面很多成熟的开源的扩展,比如py的matlab扩展包,都是使用的pylist_append,难道他们测试的时候就没发现内存持续增长吗??这么看来,如此成熟的开源的东西也不一定可靠。尽信书不如无书,两个一样的道理。

    2,不建议直接用c写扩展,比较难把握,出问题的时候没有好的调试工具,建议:简单的代码直接使用ctype,复杂的代码可以使用c来简单封装下,然后写成so给py用ctype调用。

    3,py的内存泄露不好调试,valgrind可以检测,但是信息不多,这里 有个图形化的工具,原理是打印出所有object信息,然后用画图工具画出来各个对象的关系,觉得比较麻烦,未曾使用




    展开全文
  • python的内存管理使用了引用计数方法,而多线程同时操作一个变量时,引用计数可能出错导致内存泄露或者异常销毁,一个解决办法当然是加锁,但是python并没有采用,一个是频繁加锁解锁影响性能,二是多个锁处理...
  • 内存泄露的根本原因在于创建了某个对象,却没有及时的释放掉,直到程序结束前,这个未被释放的对象一直占着内存。那这样有什么问题吗?其实量少的话还好,如果量大那么就会直接把内存占满,导致程序被kill掉,这就是...
  • Python上下文管理器

    2021-03-24 15:41:39
    作为一个程序员或者准程序员,一定听说过内存泄露,内存泄露的根本原因在于 创建了某个对象,却没有及时的释放掉,直到程序结束前,这个未被释放的对象一直占着内存。 那这样有什么问题吗?其实量少的话还好,如果量...
  • python 高级编程 一

    千次阅读 2013-10-29 22:18:47
    最近迷上了python,感觉python的语法简洁,功能丰富又强大。... 以前用python 写过一个多线程爬虫,长时间抓取网页,结果出现了内存泄露,一直都找到原因,抓取个两三个小时就会因为内存爆掉而停止。
  • pyclutter内存不释放解决记录

    千次阅读 2012-04-20 15:35:47
    开始时,一直怀疑是内存泄露的原因,但是前面的工作将内存泄露的原因也排除了. 我用c写的程序在pc上运行,没有明显的内存泄露. 后来,我将两个一模一样的程序,分别在两个平台上运行,得到的内存记录却完全不同,...
  • 可以看到原因:虚拟机总共内存8082608KB,结果python项目就消耗掉了7341764KB,内存泄露,导致python进程被系统杀死 顺带介绍下watch命令: 这个命令可以实时跟新监控内容 1、tailf log/web.log 就会看到...
  • 某东公司面试题

    千次阅读 2015-01-18 20:34:12
    由于最近比较躁动,动了跳槽念想,于是便有了这次经历,在这里记录下来,分享给大家。好了废话不多说,开始正文。...3.Java中内存泄露有几种?如何分析泄露原因? 4.UI线程与非UI线程区别?如何交
  • 上班时候,用C++在Solaris上写程序,还需要用Purify查内存泄露,用dbx和MDB跟踪程序,用Python写测试脚本。回到家,学习和开发iPhone,感觉真很累。其实之前我答应了在第一届iPhone开发者大会上做一些演讲,...
  • Memory leak 攻略

    千次阅读 2010-07-22 16:13:00
    防止内存泄露例如c++中使用auto_ptr, java中自己垃圾回收。对于纯java, python的语言编写东西,memory leak一般不是啥米问题,这些语言最严重问题是内存不足。这些java要处理内存不足就-Xmx1024m或者开更大...
  •  * TR1 正则表达式错误导致内存泄露  * 粘贴命令不工作, 如果剪贴板文本为不可用 Unicode  * 如果路径包含 '.', 则 .txt 文件扩展名不能被自动追加  * 文字加亮不能在拆分窗格进行加亮  * 如果出现多个候选...
  • 目录介绍 01.项目介绍 02.项目运行 03.项目部分介绍 ...有建议Clean然后Rebuild,有建议修改使用内存,有说是代码问题,也有说是资源问题,比如本来是jpg图片或者.9图片,文件后缀却是png也会导致...
  • Redisson : Redisson是架设在Redis基础上一个 Java 驻内存数据网格(In-Memory Data Grid),支持超过 30 个对象和服务:Set,SortedSet, Map, List, Queue, Deque ......。更多介绍请看:《Redisson 项目介绍》 ...

空空如也

空空如也

1 2
收藏数 24
精华内容 9
关键字:

python内存泄露的原因

python 订阅