Python内存管理和gc模块

2021年5月11日 / 525次阅读 / Last Modified 2021年5月11日

python内存管理基本概念

Python中一切都是对象,变量只是对象的引用。对象要占用内存,python中有一个内存池的概念。

当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。内存池的作用就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。

内存池不够,再申请新的内容!

大内存直接malloc,小内存用内存池,256K为分界线。

Python通过对象引用计数(reference count)的方式来管理内存,当某个对象的引用计数为0之后,垃圾回收时,就会将这样的对象清理掉。对于存在链式引用,或者循环引用的场景,当这一堆相互引用的对象变成unreachable时,即没有办法用正常的代码再获取他们的引用时(没有变量指向他们中的任何一个),垃圾回收时,也会将他们回收。但是要慎用 __del__,具体参考:Python中的内存泄露

给变量赋值(给对象分配一个名称),将对象加入某个container,这些操作都会使引用计数增加。反过来就是引用计数减少。sys.getrefcount函数可以得到某个对象当前的引用数量,但是要记得 -1 。具体参考:用sys.getrefcount查看对象引用计数

垃圾回收的启动时机

默认情况下,python解释器会启动垃圾回收机制,在垃圾回收期间,程序会暂停运行!

>>> import gc
>>> gc.get_threshold()
(700, 10, 10)

python的垃圾回收,还有个分代(generation)的概念,共3代,0,1和2!

The GC classifies objects into three generations depending on how many collection sweeps they have survived. New objects are placed in the youngest generation (generation 0). If an object survives a collection it is moved into the next older generation. Since generation 2 is the oldest generation, objects in that generation remain there after a collection. In order to decide when to run, the collector keeps track of the number object allocations and deallocations since the last collection. When the number of allocations minus the number of deallocations exceeds threshold0, collection starts. Initially only generation 0 is examined. If generation 0 has been examined more than threshold1 times since generation 1 has been examined, then generation 1 is examined as well. With the third generation, things are a bit more complicated, see Collecting the oldest generation for more information.

gc.get_threshold()返回的tuple,就是这3代回收的设置参数,具体含义为:

  • 700表示当前分配的对象和取消分配的对象的差,当达到700时,启动0代回收;
  • 中间的10表示,执行10次0代回收,然后执行1次1代回收;
  • 最后的10表示,执行10次1代回收,然后执行1次2代回收;

分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90%之间。 因此,简单地认为:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度,是一种以空间换时间的方法策略

gc.set_threshold()函数可以修改默认配置,但建议除非非常特殊的情况,而且你要理解你在做什么。可以设置为0,表示disable!另一个整体disable垃圾回收的接口是 gc.disable() ,一般 disable 后,需要程序在某些地方手动执行垃圾回收,调用 gc.collect() 函数实现一次手动回收,此函数只要调用就执行垃圾回收,不管是否自动机制被关闭与否。

gc模块

gc模块是我们在python中进行内存管理的接口,一般情况python程序员都不用关心自己程序的内存管理问题,但是有的时候,比如发现自己程序存在内存泄露,就可能需要用到gc模块的接口来排查问题。

有的python系统会关闭自动垃圾回收,程序自己判断回收的时机,据说instagram的系统就是这样做的,整体运行效率提高了10%。

对内存泄露问题,基本思路是找到应该被释放掉,但因为还存在引用,而没有释放的对象。下面示例代码:

import sys
import gc
from pprint import pprint


class xyz(): pass

x = xyz()
y = xyz()
z = xyz()
print('x', hex(id(x)))
print('y', hex(id(y)))
print('z', hex(id(z)))

x.y = y
x.z = z
print('x.__dict__', hex(id(x.__dict__)))

ref_xyz = []
for it in gc.get_objects():
    if type(it).__name__ == 'xyz':
        ref_xyz.append(it)

print('----')
print('ref_xyz', hex(id(ref_xyz)))
for it in ref_xyz:
    print(hex(id(it)))

print('----')
ref_z = gc.get_referrers(z)
print(len(ref_z))
print('sys.getrefcount(z)-1', sys.getrefcount(z)-1)
pprint(ref_z)
for it in ref_z:
    print(type(it).__name__, hex(id(it)))

del ref_z
print('sys.getrefcount(z)-1 after del ref_z', sys.getrefcount(z)-1)

运行效果如下:

E:\py>python test_gc.py
x 0x2275508
y 0x2275548
z 0x2275b48
x.__dict__ 0x1dc39a8
----
ref_xyz 0x25c4b48
0x2275508
0x2275548
0x2275b48
----
3
sys.getrefcount(z)-1 4
[{'y': <__main__.xyz object at 0x0000000002275548>,
  'z': <__main__.xyz object at 0x0000000002275B48>},
 [<__main__.xyz object at 0x0000000002275508>,
  <__main__.xyz object at 0x0000000002275548>,
  <__main__.xyz object at 0x0000000002275B48>],
 {'__annotations__': {},
  '__builtins__': ,
  '__cached__': None,
  '__doc__': None,
  '__file__': 'test_gc.py',
  '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000
001E805C8>,
  '__name__': '__main__',
  '__package__': None,
  '__spec__': None,
  'gc': ,
  'it': <__main__.xyz object at 0x0000000002275B48>,
  'pprint': ,
  'ref_xyz': [<__main__.xyz object at 0x0000000002275508>,
              <__main__.xyz object at 0x0000000002275548>,
              <__main__.xyz object at 0x0000000002275B48>],
  'ref_z': ,
  'sys': ,
  'x': <__main__.xyz object at 0x0000000002275508>,
  'xyz': ,
  'y': <__main__.xyz object at 0x0000000002275548>,
  'z': <__main__.xyz object at 0x0000000002275B48>}]
dict 0x1dc39a8
list 0x25c4b48
dict 0x4c0098
sys.getrefcount(z)-1 after del ref_z 3

gc.get_objects()函数返回GC在跟踪(track)的对象,注意,python中的原子类型不会跟track。

gc.is_tracked(obj)

Returns True if the object is currently tracked by the garbage collector, False otherwise. As a general rule, instances of atomic types aren’t tracked and instances of non-atomic types (containers, user-defined objects…) are. However, some type-specific optimizations can be present in order to suppress the garbage collector footprint of simple instances (e.g. dicts containing only atomic keys and values):

>>> gc.is_tracked(0)
False
>>> gc.is_tracked("a")
False
>>> gc.is_tracked([])
True
>>> gc.is_tracked({})
False
>>> gc.is_tracked({"a": 1})
False
>>> gc.is_tracked({"a": []})
True

gc.get_referrers,用来得到参数对象的引用对象,注意此函数返回的list也构成了另一个引用!因此,测试代码最后由一个del测试。z对象有3个引用,一个是z这个名称,它存在于python解释器顶层的dict中;一个在x.z中,地址为x.__dict__这个dict的地址;最后一个在ref_xyz这个list中!

找到那些不该存在的对象的引用,是解决python内存泄露的根本!除了上面测试代码那样,还可以设置gc.DEBUG_LEAK标志,然后去查看gc.garbage这个这个list!

gc模块一般用不到,要用都是救命的时候:https://docs.python.org/3/library/gc.htm

-- EOF --

本文链接:https://www.pynote.net/archives/3655

相关文章

    留言区

    《Python内存管理和gc模块》有2条留言

    您的电子邮箱地址不会被公开。 必填项已用*标注

    • 麦新杰

      CPython implementation detail: CPython currently uses a reference-counting scheme with (optional) delayed detection of cyclically linked garbage, which collects most objects as soon as they become unreachable, but is not guaranteed to collect garbage containing circular references. See the documentation of the gc module for information on controlling the collection of cyclic garbage. Other implementations act differently and CPython may change. Do not depend on immediate finalization of objects when they become unreachable (so you should always close files explicitly). [回复]

    • 麦新杰

      gc模块不会跟踪所有的对象,可以这样理解:int和str,不存在去引用其它用户定义对象的可能性,因此就不跟踪了;而container对象,很轻松就能够去引用别的对象,当然就要跟踪起来啦。 [回复]


    前一篇:
    后一篇:

    More

    麦新杰的Python笔记

    Ctrl+D 收藏本页


    ©Copyright 麦新杰 Since 2019 Python笔记

    go to top