垃圾回收机制

本文详细介绍了Python的垃圾回收机制,包括引用计数法、标记-清除以及分代回收。引用计数法简单但存在循环引用问题,标记-清除解决了循环引用,而分代回收通过空间换取时间提高效率。垃圾回收在Python中至关重要,对于内存管理有着深远影响。
摘要由CSDN通过智能技术生成

垃圾回收机制(简称GC)是python解释器自带的一种机制,专门用来回收不可用的变量值所占用的内存空间,主要由gc模块实现。而gc模块则是采用了引用计数法为主标记-清除和分代回收两种机制为辅的策略实现了垃圾回收机制
其中,引用计数法用于跟踪和回收垃圾,在引用计数法的基础上,通过标记-清除机制解决容器对象可能产生的循环引用的问题,最后通过分代回收机制以空间换取时间的方式提高垃圾回收的效率


'引用计数:就是变量值被变量名关联的次数'
# a = “A” 数据A被变量名a 关联了,所以数据A的引用次数+1
# b = “a” 数据“a”被变量名b关联了,所以数据“a”的引用次数+1

引用计数法的原理是每个对象维护一个ob_refcnt 引用计数器(整型)用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象
当发生以下4中情况的时候,对象的引用计数器ob_refcnt+1:

条件举例
对象被创建a = 100
对象被引用

b = a

对象被作为参数,传到函数中func(a)
对象作为一个元素,保存在容器类型的数据中data = {a,“a”,“b”}

当发生以下四种情况时,对象引用计数器ob_refcnt-1,当指向该对象的内存的引用计数器为0时,该内存将会被python虚拟机销毁

 条件举例
当该对象的别名被显示销毁时del a
当该对象的引用别名被赋予新的对象a=2
当该对象离开它的作用域,如func函数执行完毕,函数中的局部变量的引用计数-1,但是全局变量与闭包不会
当该对象从容器中删除时,或容器本身被销毁时del data

可以通过sys.getrefcount(变量)来查看对象的引用次数

import sys
class Person(object):
    pass
p = Person()
print(sys.getrefcount(Person()))

以上,Person() 本身代表1个对象,但是该对象并没有进行赋值,所以此处的引用次数为1,是因为对象作为参数传入sys.getrefcount 而增加的

print(sys.getrefcount(p))  

p 经过赋值引用了Person 的实例对象,然后把p作为参数传递给sys.getrefcount, 所以此处引用了2次。

因为小数据池和python驻留机制的原因,针对数字,字符串,空元组等小数据在系统属于常驻内存,为公用对象,所以它们的引用次数是个未知数。、

引用计数器机制的优缺点:

优点:简单,实时,一旦没有引用,内存就直接释放了。处理回收内存的时间分散,而其他两种机制需要等待特定时机,时间相对集中

缺点:维护引用计数消耗资源,引用次数的增加或减少都会引用机制的执行,容易出现循环引用的情况。

标记-清除

上面提到的引用计数法的缺点- 循环引用的问题如下:

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

list1 与 list2 相互作为对方成员,就会造成循环引用,即便不存在其他对象对它们的引用,list1与list2 的引用计数也仍然为1,所占用的内存永远无法被回收,引用计数法对此是无解的,因此必须要使用其他的垃圾回收机制对其进行补充

import sys
import ctypes
list1 = ["list1"]
list2 = ["list1", ]
list1.append(list2)
list2.append(list1)
print(list1)
print(list2)

address = id(list1)
list2_id = id(list2)

print(sys.getrefcount(list1))
print(sys.getrefcount(list2))

del list1
del list2

print(sys.getrefcount(ctypes.cast(address, ctypes.py_object).value))

以上代码运行结果可以看出来,list1和list2 变量即使被删除了,还是驻留内存中。除了列表,所有容器对象(list,set,dict,class,instance)都可以包含对其他对象的引用,所以都有可能产生循环引用问题,而标记-清除机制就是为了解决循环引用的问题而设计的,只针对容器类型。

标记阶段(mark):遍历程序内部的可能会造成的循环引用容器对象,如果还有其他对象引用该容器对象,则把该容器对象标记为可达(reachable)在python解释器内部实际上遍历所有的栈区中GC Roots对象(也就是所有保存在栈区中的变量名等内容)并把所有GC Roots对象直接或间接关联起来的对象标记为可达状态,没有关联的则不会被标记

清除阶段(sweep):再次遍历程序内部的可能会造成循环引用容器对象,如果发现某个对象没有标记为可达,就将其回收,从而解决容器类型数据带来的循环引用问题

标记清除的优点在于可以解决循环引用的问题,并且在整个算法执行的过程中没有额外的开销,

缺点:1,当执行标记清除时正常的程序将会被阻塞,

          2,标记清除算法在执行很多次数后,程序的堆空间会产生一些小的内存碎片。

分代回收

基于标记-清除这种回收机制,每次gc回收内存时,都需要把所有容器对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率。

分代回收核心思想是在历经多次垃圾回收扫描以后,都没有被回收的对象,gc机制就会认为,该对象是常用数据,gc机制对其扫描的频率就会降低,在执行标记-清除机制时可以有效减小遍历的对象数量,从而提高垃圾回收的速度,是一种以空间换时间的方法策略

所谓的分代指的是根据存活时间来把变量划分不同等级,在python解释器内部通过NUM_GENERATIONS变量来设置为3代,分为0(新生代),1(青春代),2(老年代)三代,所有的新建对象都是0代对象,当某一代对象经历过垃圾回收,依然存活,就被归入下一代对象(按新生代>青春代>老年代顺序)每个代都由一个gc_generation 数据结构来保存。

gc模块的分代回收机制策略如下:

--每新增701个需要GC的对象,触发一次新生代 GC

--每执行11次新生代GC,触发一次中生代GC

--每执行11次中生代GC, 触发一次老生代GC(老生代GC还受其他策略影响,频率更低)

例如,假设阀值默认不变的情况下,(700,10,10),我们可以通过gc.get_count来查看gc机制自动执行垃圾回收的计数器。

import gc

print(gc.get_threshold())  # (700, 10, 10)

class A():
    pass

print(gc.get_count()) # (493, 2, 1)
a = A()
print(gc.get_count()) # (494, 2, 1)
del a
print(gc.get_count()) # (493, 2, 1)

当计数器从(699,2,1)增加到(700,2,1)gc模块就会执行一次垃圾回收gc.collect(0),即检查一次对象种的垃圾,并重置计数器为(0,3,1)

当计数器从(699,9,1)增加到(700,9,1)gc模块就会执行一次垃圾回收gc.collect(1),即检查一,二代对象的垃圾,并重置计数器为(0,0,2)

当计数器从(699,9,9)增加到(700,9,9)gc模块就会执行一次垃圾回收gc.collect(2),即检查一,二,三代对象的垃圾,并重置计数器为(0,0,0)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值