Python采用的是引用计数机制为主,标记-清理和分代收集两种机制为辅的策略。
python中一切皆对象,所以python底层计数结构地就可以抽象为:
引用计数结构体{
引用计数;
引用的对象
}
是不是简单明了。现在我们先去考虑一下,什么情况下引用计数+1,什么情况下-1,当引用次数为0时,肯定就是需要进行回收的时刻。
引用计数+1的情况
1、对象被创建时,例如 mark="帅哥"
2、对象被copy引用时,例如 mark2=mark,此时mark引用计数+1
3、对象被作为参数,传入到一个函数中时
4、对象作为一个子元素,存储到容器中时,例如 list=[mark,mark2]
引用计数-1的情况
1、对象别名被显式销毁,例如 del mark
2、对象引用被赋予新的对象,例如mark2=mark3,此时mark引用计数-1(对照引用计数+1的情况下的第二点来看)
3、一个函数离开他的作用域,例如函数执行完成,它的引用参数的引用计数-1
4、对象所在容器被销毁,或者从容器中删除。
查看引用计数
实例:
import sys
a = "mark 帅哥"
print(sys.getrefcount(a))
引用计数机制优点
1、简单、直观
2、实时性,只要没有了引用就释放资源。
引用计数机制缺点
1、维护引用计数需要消耗一定的资源
2、循环应用时,无法回收。也正是因为这个原因,才需要通过标记-清理和分代收集机制来辅助引用计数机制。
由上面内容我们可以知道,引用计数机制有两个缺点,缺点1还可以勉强让人接受,缺点2如果不解决,肯定会引起内存泄露,为了解决这个问题,引入了标记删除。
我们先来看个实例,从实例中领会标记删除:
a=[1,2]#假设此时a的引用为1
b=[3,4]#假设此时b的引用为1
#循环引用
a.append(b)#b的引用+1=2
b.append(a)//a的引用+1=2
假如现在需要删除a,应该如何回收呢?(注意删除a可以使用del a,这样a这个引用就不存在了,但是它指向的对象,在标记删除后还存在,因为还被b使用者)
c=[5,6]#假设此时c的引用为1
d=[7,8]#假设此时d的引用为1
#循环引用
c.append(d)#c的引用+1=2
d.append(c)#d的引用+1=2
假如现在需要同时删除c、d,应该如何回收呢?
首先我们应该已经知道,不管上面两种情况的哪一个都无法只通过计数来完成回收,因为随便删除一个变量,它的引用只会-1,变成1,还是大于0,不会回收,为了解决这个问题,开始看标记删除来大展神威吧。
puthon标记删除时通过l两个容器来完成的:死亡容器、存活容器。
首先,我们先来分析情况2,删除c、d
删除后,c的引用为1,d的引用为1,根据引用计数,还无法删除
标记删除第一步:对执行删除操作后的每个引用-1,此时c的引用为0,d的引用为0,把他们都放到死亡容器内。把那些引用仍然大于0的放到存活容器内。
标记删除第二步:遍历存活容器,查看是否有的存活容器引用了死亡容器内的对象,如果有就把该对象(注意是对象,比如0x7f94bb602f80,不是对象的引用)从死亡容器内取出,放到存活容器内。
由于c、d都没有对象引用他们了,所以经过这一步骤,他们还是在死亡组。
标记删除第三部:将死亡组所有对象删除。
这样就完成了对从c、d的删除。
同样道理,我们来分析:只删除a的过程:
标记删除第一步:对执行删除(-1)后的每个引用-1,那么a的引用就是0,b的引用为1,将a放到死亡容器,将b放到存活容器。
标记删除第二步:循环存活容器,发现b引用a,复活a:将a放到存活容器内。
标记删除第三步:删除死亡容器内的所有对象。
综上所说,发现对于循环引用,必须将循环引用的双发对象都删除,才可以被回收。
标记-清理就是这么简单,。
经过上面的【标记-清理】方法,已经可以保证对垃圾的回收了,但还有一个问题,【标记-清理】什么时候执行比较好呢,是对所有对象都同时执行吗?
同时执行很显然不合理,我们知道,存活越久的对象,说明他的引用更持久(好像是个屁话,引用不持久就被删除了),为了更合理的进行【标记-删除】,就需要对对象进行分代处理,思路很简单:
1、新创建的对象做为0代
2、每执行一个【标记-删除】,存活的对象代数就+1
3、代数越高的对象(存活越持久的对象),进行【标记-删除】的时间间隔就越长。这个间隔,江湖人称阀值。
是不是很简单呢。
1、调用gc.collect()
2、GC达到阀值时
3、程序退出时
由于整数使用广泛,为了避免为整数频繁销毁、申请内存空间,引入了小整数对象池。[-5,257)是提前定义好的,不会销毁,单个字母也是。
那对于其他整数,或者其他字符串的不可变类型,如果存在重复的多个,例如:
a1="mark"
a2="mark"
a3="mark"
a4="mark"
....
a1000="mark"
如果每次声明都开辟出一段空间,很显然不合理,这个时候python就会使用intern机制,靠引用计数来维护。
总计:
1、小整数[-5,257):共用对象,常驻内存
2、单个字符:共用对象,常驻内存
3、单个单词等不可变类型,默认开启intern机制,共用对象,引用计数为0时销毁。
1 问题:
2 假设:
1. 越命大的对象, 越长寿
2. 假设一个对象10次检测都没给它干掉, 就认定这个对象一定很长寿, 就减少这货的"检测频率"
3 设计机制:
分待回收
机制
1. 默认一个对象被创建出来后, 属于 0 代
2. 如果经历过这一代"垃圾回收"后, 依然存活, 则划分到下一代
3. "垃圾回收"的周期顺序为
0代"垃圾回收"一定次数, 会触发 0代和1代回收
1代"垃圾回收"一定次数, 会触发0代, 1代和2代回收
import gc
# 获取
print(gc.get_threshold())
>>>> 打印结果
(700, 10, 10)
# 参数1,700;代表:新增的对象个数-消亡的对象个数 == 700 时会促发垃圾检测时机
# 参数2,10;代表:当第0代对象检测次数达到10次时候,会促发0代和1代对象的检测
# 参数3,10;代表:当第1代对象检测次数达到10次时候,会促发0代、1代和2代对象的检测
# 设置
gc.set_threshold(200, 5, 5)
print(gc.get_threshold())
>>>> 打印结果
(200, 5, 5)
* 开启垃圾回收机制
* 且达到启动垃圾回收对应的阈值
gc.enable()
开启垃圾回收机制(默认开启)
gc.disable()
关闭垃圾回收机制
gc.isenabled()
判定是否开启
查看方法
gc.get_threshold()
获取自动回收阈值
gc.set_threshold()
设置自动回收阈值
# 自动回收
import gc
gc.disable()
print(gc.isenabled())
gc.enable()
print(gc.isenabled())
print(gc.get_threshold())
gc.set_threshold(1000, 15, 5)
一般会将对应的阈值设置成更大的值,这样可以提高程序性能
gc.collect(generation=None)
"""
collect([generation]) -> n
With no arguments, run a full collection. The optional argument
may be an integer specifying which generation to collect. A ValueError
is raised if the generation number is invalid.
The number of unreachable objects is returned.
"""
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
1
1
import objgraph
import gc
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手动触发垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
import objgraph
import gc
gc.disable() #手动关闭垃圾回收
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手动启动垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
print(gc.isenabled()) # 检查关闭后是否自动开启
>>>> 打印结果
0
0
False