python的垃圾回收机制

Python中的垃圾回收算法是采用引用计数, 当一个对象的引用计数为0时, Python的垃圾回收机制就会将对象回收

?
1
2
a = "larry"
b = a

larry这个字符串对象, 在第一行被贴了a标签后, 引用计数为1, 之后在第二行, 由贴上了b标签, 此时, 该字符串对象的引用计数为

?
1
2
3
a = "larry"
b = a
del a

注意: 在Python语言中, del语句操作某个对象的时候, 并不是直接将该对象在内存中删除, 而是将该对象的引用计数-1,只是删除变量a对字符串的引用,实际上字符串并没有在内存删除,相当于工作岗位是有的,只是单独的解除了劳动合同。

?
1
2
3
4
5
6
7
8
9
10
>>> a = "larry"
>>> b = a
>>> del a
>>> id (b)
4572141808
>>> id (a)
Traceback (most recent call last):
  File "<input>" , line 1 , in <module>
   id (a)
NameError: name 'a' is not defined

从以上示例中可以看出, larry这个字符串对象在第一行被贴上了a标签, 此时字符串对象的引用计数为1, 接着第二行又被贴上了b标签, 此时该字符串对象的引用计数为2, 在第三行中, del语言删除了a变量(标签), 在后续的print中可以看出, 内存中实际的字符串对象并没有被删除, del语言只是删除了一个变量对该字符串对象的引用, 所以对于larry这个字符串对象来说, 效果只是引用计数-1

引用计数案例

import sys
class A:
    def __init__(self):
        '''初始化对象'''
        print('object born id:%s' %str(hex(id(self))))  # 对象被引用增加一次

def f1():
    '''循环引用变量与删除变量'''
    while True:
        c1=A()
        del c1

def func(c):
    print('obejct refcount is: ',sys.getrefcount(c)) #getrefcount()方法用于返回对象的引用计数   对象被引用


if __name__ == '__main__':
   #生成对象
    a=A()    # 创建对象引用计数增加一次
    func(a)  # 作为参数传给函数一用一次

    #增加引用
    b=a
    func(a)

    #销毁引用对象b
    del b
    func(a)
# 打印结果
object born id:0x265c56a56d8 obejct refcount is: 4 obejct refcount is: 5 obejct refcount is: 4

 

导致引用计数+1的情况

对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a],如果容器被删除,则引用响应的减1

 

导致引用计数-1的情况

对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象

 

 **注:**重复相同的操作引用计数不变,如上面例子,多次执行func(a),并不会改变计数。

  

魔法函数之__del__

类中的__del__魔法函数, 支持我们自定义清理对象的逻辑, 当Python解释器使用del语言删除类的对象的时候, 会自动调用类中的__del__函数, 我们可以对其进行重载

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> class Ref:
...
...   def __init__( self , name):
...     self .name = name
...
...   def __del__( self ):
...     print ( "删除对象" )
...     del self .name
...
>>>
>>> r = Ref(name = "larry" )
>>> print (r.name)
larry
>>>
>>> del r

删除对象

?
1
2
3
4
5
6
>>>
>>> print (r.name)
Traceback (most recent call last):
  File "<input>" , line 1 , in <module>
   print (r.name)
NameError: name 'r' is not defined

我们可以通过重载__del__魔法函数, 自己灵活控制在del 对象的时候执行哪些操作。

引用计数-循环引用

垃圾回收机制中,循环引用会导致内存泄露,举例说明

a = {} # 变量a指向A内存地址,引用计数为1
b = {} # 变量b指向B内存地址,引用计数为1
# 将b作为a中的一个值,即在A内存地址中,存了B,此时B引用计数为2
a["b"] = b
b["a"] = a
# 此时A和B的引用计数均为2
del a
del b
 
# A和B对应的引用计数均减1,此时A和B引用计数都是1
# 因为在A的内存地址中存着B,B指向的内存地址中存着A
# 此时A,B均任何引用指向,但是这两个对象包含着对方对象的引用,这两个对象无法被其他变量引用,但是引用计数并没有减到0,此时造成内存泄露.

 

标记清除

"标记-清除"是为了解决循环引用的问题.可以包含其他对象引用的容器对象(比如:list,set,dict,class,instance)都可能产生循环引用.

标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。

标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

分代回收

分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象

 

参考:https://www.jb51.net/article/150881.htm

转载于:https://www.cnblogs.com/liuweida/p/11408504.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值