python-pyramid 应用内存完全不释放

7 投票
2 回答
1177 浏览
提问于 2025-04-18 16:31

如何解决这个内存泄漏问题?

我应该采取什么措施来清理旧的会话对象?难道 session.close() 就足够了吗?

或者

这和Pyramid框架有关吗?

Sqlalchmey setup:
----------------------------------------------------------------------------------
def get_db(request):
    maker = request.registry.dbmaker
    session = maker()

    @profile
    def cleanup(request):
        _session = request.db
        if request.exception is not None:
            _session.rollback()
        else:
            _session.commit()
        _session.close()
        # del _session     # No memory released

    request.add_finished_callback(cleanup)
    return session

def main(global_config, **settings):
    :
    :
    config.registry.dbmaker = sessionmaker(bind=engine)
    config.add_request_method(get_db, name='db', reify=True)
    :
    :

Pyramid应用的请求处理器是这样的

@view_config(route_name='list_employees', renderer='json')
def employees(request):
   session = request.db
   office = session.query(Office).get(1)
   employees = [x.name for x in office.employees]
   return employees

现在的问题是,每次请求列表员工时,内存都在增长。内存的增加量几乎等于 office.employees. 的大小。

调试:

request 1 starts with memory utilization = 10MB
request 1 ends with memory utilization = 18MB

request 2 starts with memory utilization = 18MB
request 2 ends with memory utilization = 26MB        

request 3 starts with memory utilization = 26MB
request 3 ends with memory utilization = 34MB        
                 :
                 :
           Grows eventually

employees = [x.name for x in office.employees]
This is the line where about 8-10MB memory utilized

为了调试,我在员工和办公室模型中添加了 __del__ 方法,看起来它们是被删除的。

我还尝试了 session.expunge(office)del officegc.collect()

我正在使用 https://pypi.python.org/pypi/memory_profiler 来调试内存使用情况。同时,我在其他请求中使用 https://pypi.python.org/pypi/transaction

没有使用调试Pyramid工具栏。

编辑:发现内存增加在这一行 (employees = [x.name for x in office.employees]),在6-7个请求后显示为零。但查询返回的行数是相同的。

编辑:添加了一个独立的应用 https://github.com/Narengowda/pyramid_sqlalchemy_app

编辑:这和SQLAlchemy完全没有关系(我错了)。我写了一个简单的视图函数,没有任何SQLAlchemy查询。

class Test(object):

    def __init__(self):
        self.x = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000
        self.y = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000
        self.z = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000
        self.i = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000
        self.v = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000
        self.o = 'sdfklhasdjkfhasklsdkjflksdfksd' *1000


@view_config(route_name='home', renderer='json')
def my_view(request):
    return test(request)

@profile
def test(request):
    count = request.GET.get('count')
    l = [Test() for i in range(int(count))]
    print l[0]
    return {}

我能看到这个,下面是请求的日志

请求:1

行号 内存使用 增量 行内容


23     37.3 MiB      0.0 MiB   @profile
24                             def test(request):
25     37.3 MiB      0.0 MiB       count = request.GET.get('count')
26    112.4 MiB     75.1 MiB       l = [Test() for i in range(int(count))]
27    112.4 MiB      0.0 MiB       print l[0]
28    112.4 MiB      0.0 MiB       return {}

请求:2

行号 内存使用 增量 行内容


23    111.7 MiB      0.0 MiB   @profile
24                             def test(request):
25    111.7 MiB      0.0 MiB       count = request.GET.get('count')
26    187.3 MiB     75.6 MiB       l = [Test() for i in range(int(count))]
27    187.3 MiB      0.0 MiB       print l[0]
28    187.3 MiB      0.0 MiB       return {}

请求:3

行号 内存使用 增量 行内容


23    184.3 MiB      0.0 MiB   @profile
24                             def test(request):
25    184.3 MiB      0.0 MiB       count = request.GET.get('count')
26    259.7 MiB     75.4 MiB       l = [Test() for i in range(int(count))]
27    259.7 MiB      0.0 MiB       print l[0]
28    259.7 MiB      0.0 MiB       return {}

请求:4

行号 内存使用 增量 行内容


23    255.1 MiB      0.0 MiB   @profile
24                             def test(request):
25    255.1 MiB      0.0 MiB       count = request.GET.get('count')
26    330.4 MiB     75.3 MiB       l = [Test() for i in range(int(count))]
27    330.4 MiB      0.0 MiB       print l[0]
28    330.4 MiB      0.0 MiB       return {}

请求:5

行号 内存使用 增量 行内容


23    328.2 MiB      0.0 MiB   @profile
24                             def test(request):
25    328.2 MiB      0.0 MiB       count = request.GET.get('count')
26    330.5 MiB      2.3 MiB       l = [Test() for i in range(int(count))]
27    330.5 MiB      0.0 MiB       print l[0]
28    330.5 MiB      0.0 MiB       return {}

请求:6

行号 内存使用 增量 行内容


23    330.5 MiB      0.0 MiB   @profile
24                             def test(request):
25    330.5 MiB      0.0 MiB       count = request.GET.get('count')
26    330.5 MiB      0.0 MiB       l = [Test() for i in range(int(count))]
27    330.5 MiB      0.0 MiB       print l[0]
28    330.5 MiB      0.0 MiB       return {}

我尝试了很多次,使用不同的计数查询参数,发现内存使用的增加在正好5个请求后停止(真是神奇)。

我还尝试打印所有对象并比较它们的地址,我观察到请求4和请求5的日志。

看起来发生了垃圾回收(GC),因此内存从330.4 MiB减少到328.2 MiB。但你不会看到创建新对象时的75.3 MiB内存使用(第26行),而你只看到2.3 MiB的增加。

后来我验证了最后两个请求中创建的对象的地址,发现最后两个请求中80%的对象地址是相同的。

请求:4对象地址

<pyramid_sqa.views.Test object at 0x3a042d0>
<pyramid_sqa.views.Test object at 0x3a04310>
<pyramid_sqa.views.Test object at 0x3a04350>
<pyramid_sqa.views.Test object at 0x3a04390>
<pyramid_sqa.views.Test object at 0x3a043d0>
<pyramid_sqa.views.Test object at 0x3a04410>
<pyramid_sqa.views.Test object at 0x3a04450>
<pyramid_sqa.views.Test object at 0x3a04490>
<pyramid_sqa.views.Test object at 0x3a044d0>
<pyramid_sqa.views.Test object at 0x3a04510>

请求:5对象地址

<pyramid_sqa.views.Test object at 0x3a04390>
<pyramid_sqa.views.Test object at 0x3a043d0>
<pyramid_sqa.views.Test object at 0x3a04410>
<pyramid_sqa.views.Test object at 0x3a04450>
<pyramid_sqa.views.Test object at 0x3a04490>
<pyramid_sqa.views.Test object at 0x3a044d0>
<pyramid_sqa.views.Test object at 0x3a04290>
<pyramid_sqa.views.Test object at 0x3a04550>
<pyramid_sqa.views.Test object at 0x3a04590>
<pyramid_sqa.views.Test object at 0x3a045d0>

所以新对象被创建,Python正在重用内存(重用对象!?)

如果我的服务器内存这样猛增,是否可以?

2 个回答

0

这就是Python的工作方式,以及它是如何管理内存的,所以从技术上讲,这并不是一个问题。

我可以给你写一段无聊的文字,详细解释为什么会这样,但这个话题太大了,而且你肯定不想去碰内存管理。不过,我找到一个网站,把这些内容总结得很好,通俗易懂:在这里

说到这里,你可以尝试的唯一方法是使用Python的垃圾回收接口,并强制它释放未使用的内存,像这样:

gc.collect()

不过要注意强制这个词,因为你通常不应该去干扰Python的垃圾回收,它知道怎么做自己的工作,而且在大多数情况下做得很好,但如果这是你唯一的选择,试试看也许值得。

3

Python会自己管理它的对象内存。即使当CPython的垃圾回收(GC)释放了一个Python对象,它也不会把内存还给操作系统(就像malloc()/free()那样)。当垃圾回收释放一个Python对象后,这块内存可以被新的Python对象使用。这就是为什么在第6个请求时,内存使用量没有增加的原因。在第5个请求后,垃圾回收释放了被删除的对象,而第6个请求中的新对象可以使用这块释放的内存。

所以你并没有出现内存泄漏,你只是了解了CPython的内存管理是怎么回事。内存使用量并不会无限增长。

撰写回答