Python的垃圾回收机制

1. 垃圾回收机制的算法分类

python垃圾回收算法通常有三类:引用计数,标记清除和分代回收,主要以引用计数为主,标记清除和分代回收为辅

2. 对象的存储方式——refchain环状双向链表

在Python中创建的任何对象都会放在refchain的双向链表中
  在这里插入图片描述
C语言中结构体的定义代码,即refchain中存在四个共性的四个属性值:prev、next、refcnt(引用计数器)、type(类型)

但是如列表、元组、字典等类型时,增加size属性,即元素个数
  在这里插入图片描述
不同的类型的不同属性
在这里插入图片描述
3. 引用计数

1. 引用计数的原理

1. 每个对象在创建时都会有一个引用计数器来记录引用次数,即ob_refcnt属性,默认值为1

2. 当对象被引用时,引用计数器会进行+1

3. 当对象不被引用或者销毁时,引用计数器会进行-1

4. 当引用计数器为0时,对象被回收,即在refchain链表中删除,释放系统内存

例:

import sys
class A:
    def __init__(self):
        print('对象被创建')

    def __del__(self):
        print('对象被销毁')

# sys.getrefcount方法用来获取指定对象计数器的值
print(sys.getrefcount(A()))     # 创建A()对象,计数器值为 1

a = A()
print(sys.getrefcount(a))       # 对象A()创建并被变量a引用,计数器值为 2

b = a
print(sys.getrefcount(b))       # 变量b等于变量a,即指向a的地址A(), 计数器为 3

a = 1
print(sys.getrefcount(b))       # 变量a指向其他对象,A()对象的引用数 -1,变为 2

b = 1    # 变量b指向其他对象,A()对象的引用数 1,同时A()对象被销毁,计数器 -1,变为 0,被回收

print('程序结束')

2. 引用计数的优缺点

1. 优点

1. 简单高效

2. 时效性高,只要当对象的计数器为0时,立即进行回收

2. 缺点

1. 维护计数器消耗大量的资源

2. 当出现字典,列表对对象的循环套用引用时,会造成对象之间互相引用,引发内存泄漏

4. 标记清除

1. 循环引用造成的后果

# 循环引用例子
class L1(dict):
    def __del__(self):
        print('对象1被销毁')

class L2(dict):
    def __del__(self):
        print('对象2被销毁')

l1 = L1()   # 对象L1创建的时候计数器为 1,,被l1引用,此时计数器为 2
l2 = L2()   # 对象L2创建的时候计数器为 1,,被l1引用,此时计数器为 2

print(sys.getrefcount(l1))      # 2
print(sys.getrefcount(l2))      # 2

l1['a'] = l2  # l1的键a引用l2,此时L2对象的引用为2,所以计数器为 3
l2['b'] = l1  # l2的键b引用l1,此时L1对象的引用为2,所以计数器为 3

print(sys.getrefcount(l1))      # 3
print(sys.getrefcount(l2))      # 3

# 字典被删除,但是字典中的值(即L1,L2对象)互相引用着,理论上用不着,但是无法被引用计数算法回收
del l1      # 删除l1,但是这里对象仍在被引用,计数器仍为 2,不会被回收
del l2      # 删除l2,但是这里对象仍在被引用,计数器仍为 2,不会被回收


print('程序结束')   # 对象在整个程序结束才被回收,而不是del的时候被回收

图解:
在这里插入图片描述
 2. 标记清除的实现原理

1. 标记:将所有的对象看做是一个点,并将对象的引用关系构造图结构,从根节点出发遍历所有的点,能访问到的点标记为“可达对象”

2. 清除:遍历所有对象,若没有被标记为“可达对象“则进行回收

class A:
    def __init__(self):
        pass

    def __del__(self):
        print('对象被销毁')

def func():
    a = A()     # a引用对象A
    b = A()     # b引用对象A
    c = A()     # c引用对象A
    d = A()     # d引用对象A
    e = c       # e引用c
    f = c       # f引用c
    # a,b对象的属性互相引用
    a.obj = b
    b.obj = a
    # 不返回a,b,表明两个变量引用被删除了
    return [d,e,f]

g = func()

print('程序结束,回收垃圾')

在这里插入图片描述
5. 分代回收
分代回收是建立在标记清除的基础上,扫描对象需要定义一个触发点,如果时时刻刻进行扫描,那么增加了程序的运行时间,通常将循环引用的对象分为三代:0代、1代和2代

0代:对象刚刚被创建时分配到0代链表

1代:经过一轮GC扫描存货下来的,放置1代链表中

2代:再次经过扫描存活下来的对象,分至2代链表中
   在这里插入图片描述
触发GC扫描机制

在这里插入图片描述

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值