Python程序到底有没有Memory Leak

为何要撰写这篇文章?

    前段时间测试虚拟机用着用着就宕机了,然后我登录终端就弹出oom的错误,并且kill掉了一个python的程序,故我接着我以前的经验就说是不是内存泄漏了?哎!就是我说了下是不是内存泄漏,然后问题来了。我们的开发同学说“python内存泄漏?” 这下可好,那我就来分析一下Python到底会不会内存溢出。

什么叫内存泄漏?

    内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。(摘:内存泄漏

演示Python的内存泄漏

    你可以说Python的内存管理是由“Python 内存管理器”来完成的,我们用户是不需要关心内存管理的,是的,你说的完全正确!虽然Python程序不可能像C/C++一样那样“忘记释放”内存没有free这样的Memory Leak,但是也会遇到“逻辑上Memory Leak”的情况。

def foo(a=[]):
    a.append(time.time())
    return a

    例如一个未处理的回溯对象,它使整个堆栈帧保持活动状态,即使该函数不再运行,将值存储在类或全局范围而不是实例范围中,并且没有意识到这一点。类中的循环引用也有一个del方法。但是具有讽刺意味的是,del的存在使得循环垃圾收集器无法清理实例。
    通过对比发现,内存确实在不断的变大。

#!/bin/python3
# encoding: utf-8

import psutil, os


def memory_usage_psutil():
    # 用来返回内存大小,单位MB
    process = psutil.Process(os.getpid())
    mem = process.memory_info()[0] / float(2 ** 20)
    return mem


def get_current_obj(a=[]):
    a.append([0] * 1000)
    return a


def sum(a):
    return a ** 2


def main():
    obj = []
    for i in range(10000):
        obj = get_current_obj(obj)
        # 正常逻辑,我们每次调用get_current_obj完成后
        # 内存都会释放,但是现实好想不是这样,内存而是
        # 一直在增加。
        if (i % 100 == 0):
            print(memory_usage_psutil())
    print("========Below is the sum function call.========")
    for j in range(10000):
        sum(j)
        if (j % 100 == 0):
            print(memory_usage_psutil())


if __name__ == '__main__':
    main()

以下是运行结果。

Python程序到底有没有Memory Leak_第1张图片
    可想而知,如果该程序是一直处理请求的后台程序,那您的服务器内存一定会被撑破的。

总结

    最后Python到底有没有内存泄漏?答案是肯定的,但是不是与您所想的C代码“忘记回收内存”而导致的内存泄漏,而是在程序的运行中“进程占用的内存莫名其妙一直再升高”。进程占用内存一直升高,与逻辑预期不一致,最后导致了内存泄漏。

参考文献

python内存管理
memory-leak-in-python-requests
示例代码:

import requests
import gc

def call():

    # call the get with a url,here I used google.com
    # get method returns a response object
    response = requests.get('https://google.com')
    
    # print the status code of response
    print("Status code",response.status_code)
    
    # After the function is been returned,
    # the response object becomes non-referenced
    return


def main():
    print("No.of tracked objects before calling get method")
    
    # gc.get_objects() returns list objects been tracked
    # by the collector.
    # print the length of object list with len function.
    print(len( gc.get_objects() ) )
    
    # make a call to the function, that calls get method.
    call()
    
    # collect method immediately free the resource of
    # non-referenced object.
    gc.collect()

    # print the length of object list with len
    # function after removing non-referenced object.
    print("No.of tracked objects after removing non-referenced objects")
    print(len( gc.get_objects() ) )


if __name__ == "__main__":
    main()

Python 官方模块OS内存泄漏
Note On some platforms, including FreeBSD and macOS, setting environ may cause memory leaks. Refer to the system documentation for putenv().
Python memory leak

你可能感兴趣的