python 内存管理

python 内存管理

  1. 内存管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    * 内存状况
    * 引用计数(使用sys包中的getrefcount(param)查看)
    * 对象引用对象
    * 垃圾回收
    * 阈值
    gc.set_threshold()
    gc.get_threshold()
    * 分代回收策略
    * 孤立"引用环"问题
  2. 内存状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    在python中,执行"x = 1"后,我们需要了解其过程: 系统先找一块内存,将数字1存储进去,然后将"x"指向当前的这块内存
    如下:
    >>> x = 1
    >>> print(x)
    1
    >>> id(x)
    37577064

    示例1:
    >>> x = 4 # 说明 x 指向了内存地址 37576992,内存地址中存储值为 4
    >>> id(x)
    37576992
    >>> y = 5 # 说明 y 指向了内存地址 37576968,内存地址中存储值为 5
    >>> id(y)
    37576968

    示例2:
    >>> x = y # 说明 x 指向了 y 所指向的内存地址
    >>> id(x)
    37576968
    >>> id(y)
    37576968

    示例3:
    >>> x = 4 # 说明 x 指向了内存地址 37576992,内存地址中存储值为 4
    >>> id(x)
    37576992
    >>> x = 5 # 说明 x 指向了内存地址 37576968,内存地址中存储值为 5
    >>> id(x)
    37576968
    在python中,一开始初始化存储在内存的东西是不可以更改的(x),我们所能更改的只是它的引用

    示例4:
    >>> x = 'hello'
    >>> y = 'hello'
    >>> id(x)
    139654005921952
    >>> id(y)
    139654005921952

    >>> x = "hello world"
    >>> y = "hello world"
    >>> id(x)
    140026311813376
    >>> id(y)
    140026311813280

    综上:
    如果是短字符, x 和 y 指向的是同一个内存引用对象地址,内存地址存储值唯一
    如果是长字符, x 和 y 指向的是不同的内存引用对象地址,即使内存地址存储值一样
  3. 内存引用计数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    引用计数记录指向对象引用的个数:当引用对象时,引用计数加1;当移除对象引用时,引用计数减1;当引用计数变为0,则被释放.引用计数无法释放"引用环"的对象

    引用计数增加:
    * 对象被创建时
    * 别名被创建
    * 被作为参数传递给函数
    * 成为容器对象的一个元素

    引用计数减少:
    * 一个本地引用离开了其作用范围,比如函数结束
    * 对象别名被销毁(del)
    * 对象别名被赋值给其他的对象
    * 对象被从一个容器对象中移除
    * 容器对象本身被销毁

    引用计数获取:
    >>> from sys import getrefcount
    >>> x = 'hello'
    >>> print(getrefcount(x))
    2
    >>> y = x
    >>> print(getrefcount(y))
    3
    >>> z = y
    >>> print(getrefcount(z))
    4
    >>> z = 'world'
    >>> print(getrefcount(z))
    2
    >>> print(getrefcount(y))
    3

    对象引用对象:
    [root@dev test]# cat obj1.py
    #!/usr/bin/env python
    class from_obj(object):
    def __init__(self,to_obj):
    self.to_obj = to_obj
    b = [1,2,3]
    a = from_obj(b)
    print(id(a.to_obj))
    print(id(b))

    [root@dev test]# python obj1.py
    139851871660944
    139851871660944
  4. 垃圾回收

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    垃圾回收机制用来回收循环应用的对象
    垃圾回收机制会根据内存的分配和释放情况的而被调用:比如分配内存的次数减去释放内存的次数大于某一个阈值的时候
    注意: 当内存溢出时,不会自动调用gc,gc更看重的是垃圾对象的个数,而不是大小.

    垃圾回收阈值:
    >>> import gc
    >>> print(gc.get_threshold())
    (700, 10, 10)
    >>> gc.set_threshold(600,10,10)
    >>> print(gc.get_threshold())
    (600, 10, 10)

    调用gc的策略有两种: 一种是固定时间间隔进行调用;另一种是基于事件的调用

    gc垃圾回收方法:
    可以发现,只有容器对象才会出现引用循环,比如:列表,字典,类,元组

    首先,为了追踪容器对象,需要每个容器对象维护两个额外的指针,用来将容器对象组成一个链表,指针分别指向前后两个容器对象,方便插入和删除操作;其次,每个容器对象还得添加gc_refs字段
    一次gc垃圾回收步骤:
    1.使得gc_refs等于容器对象的引用计数
    2.遍历每个容器对象(a),找到它(a)所引用的其它容器对象(b),将那个容器对象(b)的gc_refs减去1
    3.将所有gc_refs大于0的容器对象(a)取出来,组成新的队列,因为这些容器对象被容器对象队列的外部所引用
    4.任何被新队列里面的容器对象,所引用的容器对象(旧队列中)也要加入到新队列里面
    5.释放旧队列里面的剩下的容器对象

    gc分代机制:
    gc采用分代(generations)的方法来管理对象,总共分为三代:generation 0,1,2
    * 新产生的对象放到第0代里面
    * 如果该对象在第0代的一次gc垃圾回收中活了下来,那么它就被放到第1代里面
    * 如果第1代里面的对象在第1代的一次gc垃圾回收中活了下来,它就被放到第2代里面

    gc.set_threshold(threshold0,threshold1,threshold2)
    设置gc每一代垃圾回收所触发的阈值:
    从上一次第0代gc后,如果分配对象的个数减去释放对象的个数大于threshold0,那么就会对第0代中的对象进行gc垃圾回收检查.
    从上一次第1代gc后,如过第0代被gc垃圾回收的次数大于threshold1,那么就会对第1代中的对象进行gc垃圾回收检查
    从上一次第2代gc后,如过第1代被gc垃圾回收的次数大于threshold2,那么就会对第2代中的对象进行gc垃圾回收检查
    如果threshold0设置为0,表示关闭分代机制


    gc常用函数:
    * gc.set_debug(flags)
    设置gc的debug日志,一般设置为gc.DEBUG_LEAK

    * gc.collect([generation])
    显式进行垃圾回收,可以输入参数:0代表只检查第一代的对象;1代表检查一,二代的对象;2代表检查一,二,三代的对象.如果不传参数,执行一个full collection,也就是等于传2

    * gc.set_threshold(threshold0[, threshold1[, threshold2])
    设置自动执行垃圾回收的频率
    例如: (700,10,10)
    当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
    当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一,二代对象的垃圾,并重置计数器为(0,0,1)
    当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

    * gc.get_count()
    获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
    例如:(488,3,0)
    488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目(注意:是内存分配,而不是引用计数的增加)
    3是指距离上一次二代垃圾检查,一代垃圾检查的次数
    0是指距离上一次三代垃圾检查,二代垃圾检查的次数