V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
Buffer2Disk
V2EX  ›  Python

关于 Python 内存泄漏

  •  
  •   Buffer2Disk · 2018-10-10 22:23:30 +08:00 · 4208 次点击
    这是一个创建于 2228 天前的主题,其中的信息可能已经有所发展或是发生改变。
    线上机器跑了一个 Python 的多线程的程序,运行了 2 天,发现貌似有内存泄漏的情况

    htop 查了下真实内存使用达到 1G 左右(机器总共是 2G 内存)

    但是 htop 上面查看 Python 程序本身只占了不到 10%的内存,整个机器上只有这一个 Python 程序

    请问大家,这是 Python 程序的 内存泄漏 还是 句柄泄漏 了?

    有没有什么好的办法来排查嘛? 我用了下检测工具查看 Python 程序自身是 dict 对象占用了最多的内存

    32 条回复    2018-11-19 16:57:38 +08:00
    billlee
        1
    billlee  
       2018-10-10 23:05:20 +08:00
    你就不能把 htop 调成按进程显示,然后按内存排序码?
    MrGba2z
        2
    MrGba2z  
       2018-10-10 23:16:16 +08:00
    你这样启动试试再看看内存
    MALLOC_ARENA_MAX=1 python app.py
    Buffer2Disk
        3
    Buffer2Disk  
    OP
       2018-10-10 23:29:44 +08:00
    @billlee 内存排序过了,整个服务器就这一个程序在跑,它占的最多,9.9%
    Buffer2Disk
        4
    Buffer2Disk  
    OP
       2018-10-11 00:15:04 +08:00
    @MrGba2z MALLOC_ARENA_MAX 不是个环境变量么,你确定这样能启动么。。。
    MrGba2z
        5
    MrGba2z  
       2018-10-11 00:32:27 +08:00 via iPhone
    @Buffer2Disk 可以的你可以写个 dummy script 试试 起码 ubuntu 上可以
    xiaoshenke
        6
    xiaoshenke  
       2018-10-11 00:36:44 +08:00 via Android
    RES197 就是 197m 内存啊
    Buffer2Disk
        7
    Buffer2Disk  
    OP
       2018-10-11 00:46:06 +08:00
    @MrGba2z 试了下你的这个参数,貌似好像有点效果,不过还是要上线 1-2 天观察看看
    Buffer2Disk
        8
    Buffer2Disk  
    OP
       2018-10-11 00:47:45 +08:00   ❤️ 1
    @MrGba2z 网上推荐的值是 4, 看看这个 https://github.com/prestodb/presto/issues/8993
    MrGba2z
        9
    MrGba2z  
       2018-10-11 01:26:32 +08:00
    @Buffer2Disk
    简单说就是
    如果 MALLOC_ARENA_MAX 设为 1 如果有明显下降 就说明这个是 glibc 的问题 或者说他(对多线程)的行为就是这样的 并不是你的程序 memory leak 了
    但是这么做会有性能损失
    别的解决办法还有 调整 M_TRIM_THRESHOLD 或者改用别的 allocator, 比如我们就换用了 tcmalloc
    至于各种办法的优劣 你需要自己衡量下.
    MrGba2z
        10
    MrGba2z  
       2018-10-11 01:32:09 +08:00
    你实际用的内存主要是看 RSS
    如果你对性能要求很高 可以假装..无视如此庞大的 VSIZE(减少线程数也能降低)

    MALLOC_ARENA_MAX 调小之后 会增大 memory congestion 的几率, tcmalloc 等能视觉上降低 VSIZE 也只是算法做了优化, 使用了更效率的 lock/unlock 机制.

    默认 glibc 在线程初始化的时候会固定分配一块大约 100M 的内存, 但因为他没被使用 所以它只是 VSIZE
    如果你有 20 个线程 每个实际用 1M 的内存
    那么结果(使用 glibc 的默认设置)就是 20M 的 RSS 2G 的 VSIZE
    aijam
        11
    aijam  
       2018-10-11 10:22:49 +08:00
    > Python 程序本身只占了不到 10%的内存
    看起来是至少有 8 个 python 进程,每个进程消耗 10%内存。
    ChristopherWu
        12
    ChristopherWu  
       2018-10-11 10:26:36 +08:00
    @aijam 对的。。top 上就占了 80%的内存了,说不定还没有截图完全其他 python 进程。
    aijam
        13
    aijam  
       2018-10-11 10:55:05 +08:00
    如果你没看到内存越用越多,你就不能说他是“泄漏”吧。一个进程 200mb (根据实际应用而定)完全可能是正常值。
    Buffer2Disk
        14
    Buffer2Disk  
    OP
       2018-10-11 17:01:40 +08:00
    @aijam 我感觉不是你这么算的,照那么算的话,光 Python 进程已经占用 90%内存了,
    但是你用 htop 或者 free -m 看 整个系统的实际使用物理内存是 50%
    Buffer2Disk
        15
    Buffer2Disk  
    OP
       2018-10-11 17:02:29 +08:00
    @aijam 实际就是内存越用越多的,第一天 500M,第二天 900M,第三天 1200M
    Buffer2Disk
        16
    Buffer2Disk  
    OP
       2018-10-11 17:05:01 +08:00
    @MrGba2z 设置了这个值好像没用,内存今天又开始慢慢增加了
    查了一篇文章说在 redhat 6,glibc 2.12 版本上面设置这个值是无效的( glibc 的 bug )
    --->https://yq.aliyun.com/articles/227924,

    不知道是不是这个原因,如果是的话只有升级系统版本到 centos7 了
    Buffer2Disk
        17
    Buffer2Disk  
    OP
       2018-10-11 17:06:05 +08:00
    上面的链接地址多了个 ,
    https://yq.aliyun.com/articles/227924
    aijam
        18
    aijam  
       2018-10-11 17:53:54 +08:00
    @Buffer2Disk > 我感觉不是你这么算的,照那么算的话,光 Python 进程已经占用 90%内存了
    一般就是把 RES 加起来的,估计父子进程可能共享了一部分 memory page (比如有 mmap 之类的调用)
    Buffer2Disk
        19
    Buffer2Disk  
    OP
       2018-10-11 18:04:02 +08:00
    @aijam

    Python 我不是很熟悉哈,感觉我开的 13 个线程,都是以进程的形式在系统存在的

    那么每个进程使用的内存,在每个时段都一模一样,这也太巧了吧

    而且除了主线程外,其他几个线程干的事情很简单,根本不会一个线程消耗到 197M 内存
    aijam
        20
    aijam  
       2018-10-11 18:50:26 +08:00
    @Buffer2Disk
    > Python 我不是很熟悉哈,感觉我开的 13 个线程,都是以进程的形式在系统存在的
    和熟不熟悉 Python 两回事,线程和进程的区别,虚拟内存等知识了解下。

    > 那么每个进程使用的内存,在每个时段都一模一样,这也太巧了吧
    道理上讲,子进程是复制父进程的内存空间的,但实际的内存使用是 copy on write 的。
    我不确定 htop 的 RES 栏给出的是复制的内存空间,还是 copy on write 实际的内存使用,估计在不同系统下显示的结果都不一定相同,所以对这些数字的解释需要谨慎。
    如果显示的是前者(复制的内存空间),就可以解释你看到的现象:RES 给出的是 fork 时复制的内存大小,而内存总使用量显示的是实际的使用大小。
    下面这个链接印证了我的说法:
    https://stackoverflow.com/questions/35596093/htop-shows-more-resident-memory-usage-than-what-the-machine-has

    > 而且除了主线程外,其他几个线程干的事情很简单,根本不会一个线程消耗到 197M 内存
    空说无凭,可能的话上代码吧。
    Buffer2Disk
        21
    Buffer2Disk  
    OP
       2018-10-11 21:25:51 +08:00
    @aijam

    cat /proc/meminfo
    MemTotal: 2046876 kB
    MemFree: 750100 kB
    Buffers: 104608 kB
    Cached: 188044 kB
    SwapCached: 0 kB
    Active: 392248 kB
    Inactive: 134392 kB
    Active(anon): 234008 kB
    Inactive(anon): 212 kB
    Active(file): 158240 kB
    Inactive(file): 134180 kB
    Unevictable: 0 kB
    Mlocked: 0 kB
    SwapTotal: 4095996 kB
    SwapFree: 4095996 kB
    Dirty: 156 kB
    Writeback: 0 kB
    AnonPages: 233992 kB
    Mapped: 9972 kB
    Shmem: 228 kB
    Slab: 404976 kB
    SReclaimable: 304236 kB
    SUnreclaim: 100740 kB
    KernelStack: 1872 kB
    PageTables: 6592 kB
    NFS_Unstable: 0 kB
    Bounce: 0 kB
    WritebackTmp: 0 kB
    CommitLimit: 5119432 kB
    Committed_AS: 281420 kB
    VmallocTotal: 34359738367 kB
    VmallocUsed: 349680 kB
    VmallocChunk: 34359380816 kB
    HardwareCorrupted: 0 kB
    AnonHugePages: 157696 kB
    HugePages_Total: 0
    HugePages_Free: 0
    HugePages_Rsvd: 0
    HugePages_Surp: 0
    Hugepagesize: 2048 kB
    DirectMap4k: 6080 kB
    DirectMap2M: 2091008 kB
    DirectMap1G: 0 kB

    从这个角度看可能更清楚一点,anon + file 占用的内存很多,active + inactive 加起来差不多 500 多 M 了,看来应该是 Python 程序里面操作的"文件"比较消耗内存?
    Buffer2Disk
        22
    Buffer2Disk  
    OP
       2018-10-11 21:30:47 +08:00
    @MrGba2z 大佬帮忙分析下上面这个,我感觉 /proc/meminfo 看的更直观一些
    Buffer2Disk
        23
    Buffer2Disk  
    OP
       2018-10-11 21:31:27 +08:00
    pmap -x 2473 | sort -nk 2 | grep anon
    00007fb4c3fff000 4 0 0 ----- [ anon ]
    00007fb4e17fc000 4 0 0 ----- [ anon ]
    00007fb4e21fd000 4 0 0 ----- [ anon ]
    00007fb4e2bfe000 4 0 0 ----- [ anon ]
    00007fb4e35ff000 4 0 0 ----- [ anon ]
    00007fb4fc3fa000 4 0 0 ----- [ anon ]
    00007fb4fcdfb000 4 0 0 ----- [ anon ]
    00007fb4fd7fc000 4 0 0 ----- [ anon ]
    00007fb4fe1fd000 4 0 0 ----- [ anon ]
    00007fb4febfe000 4 0 0 ----- [ anon ]
    00007fb4ff5ff000 4 0 0 ----- [ anon ]
    00007fb503fff000 4 0 0 ----- [ anon ]
    00007fb504993000 4 0 0 ----- [ anon ]
    00007fb505394000 4 0 0 ----- [ anon ]
    00007fb507be5000 4 4 4 rw--- [ anon ]
    00007fb50864f000 4 0 0 rw--- [ anon ]
    00007fb511cc7000 4 4 4 rw--- [ anon ]
    00007fff29bff000 4 4 0 r-x-- [ anon ]
    ffffffffff600000 4 0 0 r-x-- [ anon ]
    00007fb507dfe000 8 4 4 rw--- [ anon ]
    00007fb511cc3000 8 8 8 rw--- [ anon ]
    00007fb509160000 16 16 16 rw--- [ anon ]
    00007fb510e53000 16 16 16 rw--- [ anon ]
    00007fb5116fb000 16 4 4 rw--- [ anon ]
    00007fb511a97000 56 56 56 rw--- [ anon ]
    00007fb4d4000000 132 36 36 rw--- [ anon ]
    00007fb5063a5000 260 256 256 rw--- [ anon ]
    00007fb506b45000 260 256 256 rw--- [ anon ]
    00007fb511c38000 540 532 532 rw--- [ anon ]
    00007fb4c8000000 584 144 144 rw--- [ anon ]
    00007fb5048d2000 772 772 772 rw--- [ anon ]
    00007fb4d8000000 856 552 552 rw--- [ anon ]
    00007fb4dc000000 1020 776 776 rw--- [ anon ]
    00007fb4f4000000 1028 1028 1028 rw--- [ anon ]
    00007fb511b02000 1040 1024 1024 rw--- [ anon ]
    00007fb4f8000000 1048 808 808 rw--- [ anon ]
    00007fb4d0000000 1184 944 944 rw--- [ anon ]
    00007fb4e4000000 1272 1040 1040 rw--- [ anon ]
    00007fb5067ee000 1292 1284 1284 rw--- [ anon ]
    00007fb4ec000000 1296 1296 1296 rw--- [ anon ]
    00007fb4f0000000 1520 1284 1284 rw--- [ anon ]
    00007fb4e8000000 1772 1544 1544 rw--- [ anon ]
    0000000001476000 3216 3100 3100 rw--- [ anon ]
    00007fb5040cb000 6148 6148 6148 rw--- [ anon ]
    00007fb4e0efa000 9224 9224 9224 rw--- [ anon ]
    00007fb4e17fd000 10240 20 20 rw--- [ anon ]
    00007fb4e21fe000 10240 20 20 rw--- [ anon ]
    00007fb4e2bff000 10240 20 20 rw--- [ anon ]
    00007fb4e3600000 10240 2048 2048 rw--- [ anon ]
    00007fb4fc3fb000 10240 20 20 rw--- [ anon ]
    00007fb4fcdfc000 10240 20 20 rw--- [ anon ]
    00007fb4fd7fd000 10240 20 20 rw--- [ anon ]
    00007fb4fe1fe000 10240 20 20 rw--- [ anon ]
    00007fb4febff000 10240 20 20 rw--- [ anon ]
    00007fb4ff600000 10240 2048 2048 rw--- [ anon ]
    00007fb504994000 10240 20 20 rw--- [ anon ]
    00007fb505395000 10240 48 48 rw--- [ anon ]
    00007fb4c4000000 19984 19024 19024 rw--- [ anon ]
    00007fb4c5384000 45552 0 0 ----- [ anon ]
    00007fb4e81bb000 63764 0 0 ----- [ anon ]
    00007fb4f017c000 64016 0 0 ----- [ anon ]
    00007fb4ec144000 64240 0 0 ----- [ anon ]
    00007fb4e413e000 64264 0 0 ----- [ anon ]
    00007fb4d0128000 64352 0 0 ----- [ anon ]
    00007fb4f8106000 64488 0 0 ----- [ anon ]
    00007fb4f4101000 64508 0 0 ----- [ anon ]
    00007fb4dc0ff000 64516 0 0 ----- [ anon ]
    00007fb4d80d6000 64680 0 0 ----- [ anon ]
    00007fb4c8092000 64952 0 0 ----- [ anon ]
    00007fb4d4021000 65404 0 0 ----- [ anon ]
    00007fb4c0000000 65532 65532 65532 rw--- [ anon ]
    00007fb500000000 65532 65532 65532 rw--- [ anon ]

    这个是 Python 进程的内存使用情况(anon)
    redsonic
        24
    redsonic  
       2018-10-12 00:07:40 +08:00
    anon 比较高没什么可说,毕竟没看代码。但 Slab 也很高啊,这个一般是内核用,比如驱动之类的。或者你的程序非常频繁申请 /注销大小差异极大的内存(造成碎片),频繁访问几十万个目录,比如一些语音文字分析程序。 可以用 slabtop 看看谁占用大头,至少可以排除掉内核的因素。
    Buffer2Disk
        25
    Buffer2Disk  
    OP
       2018-10-14 00:29:27 +08:00
    @redsonic
    我看了下 /proc/meminfo , 感觉 Slab + AnonPages + python 进程的 RES 内存 = 系统的真实使用内存,不知道这么算对不对?

    下面是 slabtop 的结果,dentry 不知道是干啥用的
    <img src=" " />
    redsonic
        26
    redsonic  
       2018-10-14 03:09:41 +08:00
    @Buffer2Disk 之前看过内存管理部分的代码,差不多 buf+cache+active+slab 就是物理用量,有差的部分是 DMA 保留区,I/O 占用,还有静态库。

    dentry 比较多的话说明打开的文件 /目录多,可以参考这个 https://yq.aliyun.com/articles/131870
    Buffer2Disk
        27
    Buffer2Disk  
    OP
       2018-10-14 11:59:47 +08:00
    @redsonic 你好,你说的 active 是指哪个 active,因为之前有看过一篇文章 Active = Active(anon) + Active(file)
    而 buffers + cached = Active(file) + Inactive(file) + Shmem,这样算的话,是不是和你那个公式里面 buffer+cache 算重复了?
    文章来源: https://lujun9972.github.io/blog/2018/04/17/meminfo%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/
    Buffer2Disk
        28
    Buffer2Disk  
    OP
       2018-10-14 12:10:03 +08:00
    @redsonic 还有一个问题,我这个 Python 多线程的程序打开的文件确实很多(fd),但是上个月的时候没有占用这么多内存,系统实际占用内存大概 600M,不知道为什么这个月就突增到 1.2G 了,fd 增长应该没有这么快,所以有点奇怪;
    感觉应该是是 fd 占用内存最多了;

    看前面的回复说,anon 和多线程有关系,我的程序里面是有 13 个线程常驻的,除了主线程外,其他的线程都是轮询从 MySQL 拿数据,然后操作 fd,之前好像也没这么消耗内存啊。。。。
    redsonic
        29
    redsonic  
       2018-10-14 12:44:27 +08:00
    @Buffer2Disk 是的,active 包含 anon 和 file 的部分。 印象中 buf 是 block io 占用的,就是读写磁盘的裸数据缓存,属于内核设施。Active(file) 和 Inactive(file)是进程相关的,应该没有关系。可以扒扒代码 fs/proc/meminfo.c,顺藤摸瓜。之前自己也扒过 committed_as 找泄漏, 如果我讲错请回复.
    redsonic
        30
    redsonic  
       2018-10-14 12:49:16 +08:00
    @Buffer2Disk 你 fd 的数据库文件很多吗?多的话可以试试预先把零碎库表写入一个文件中。以前见过类似优化的。 还有像 iOS 就把自带应用的 dyly 动态库文件合并成一个大文件,减小内存占用和访问时间。
    Buffer2Disk
        31
    Buffer2Disk  
    OP
       2018-11-16 11:25:39 +08:00
    @MrGba2z @redsonic
    花了一个多月排查,过程有点艰辛,不过总算有点成果
    首先是换了 centos7 的系统,启用了 tcmaloc,然后解决了代码里面一些存在内存泄漏的地方,内存增长终于可以保持较稳定的状态
    不过接下来碰到一个诡异的问题,内存总是在以 4kb 的速度不断往上缓慢增长,起先我以为是 python 的官方库 ordeedDict 问题 ,因为有些文章里面提到过 ordeedDict 的实现存在循环引用的情况,在 del 的时候会产生一个 self-refering list

    不过后来发现好像不是这个原因 ,去掉了 tcmalloc,export MALLOC_ARENA_MAX=1 后,好像这个诡异的内存不断增长问题就消失了

    使用的工具包括 objgraph 和 gc,pympler 等
    参考教程 http://blog.soliloquize.org/2017/10/01/Python%E4%BB%A3%E7%A0%81%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E5%88%86%E6%9E%90%E7%BB%AD/
    Buffer2Disk
        32
    Buffer2Disk  
    OP
       2018-11-19 16:57:38 +08:00
    今天从 tcmalloc 换了 jemalloc,好像效果更好了,听说 jemalloc 处理内存碎片方面更有优势( tcmalloc 在线程频繁创建和销毁的场景下更有优势,但是我的业务系统线程数恒定)
    看来可能是 python EventLoop 不断创建对象,销毁对象 造成了大量的内存碎片,然后 glibc 默认的 ptmalloc 处理内存碎片不够好造成的?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2577 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 01:41 · PVG 09:41 · LAX 17:41 · JFK 20:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.