3. 垃圾回收机制
1. 什么是 “垃圾” ?
x = 1 //把 “1” 这个值赋给 “x”,在计算机中,先是定义了一个变量x,然后在内存里面开辟了一块内存空间,用来存放x的值,也就是1,这个时候,x和1是绑定的。
这种绑定关系,叫做 “引用计数”。,这时候,“1”的引用计数就是1.
如果在这个时候,再一次给x赋值,x = 2,那么这个时候,计算机就重新开辟了一块内存空间,用来存放x的值,也就是2,存放了2之后,x和之前的1就自动解绑了。
这个时候,1的引用计数就变成0了,因为没有被引用,无法被访问到,计算机就把它视为“垃圾”。
2. 什么是垃圾回收机制
垃圾回收机制(Garbage Collection,简称 “GC”)是python解释器自带的一种机制,专门用来回收不可用变量值所占用的内存空间
Python的垃圾回收机制主要采用的是引用计数为主、标记清除与隔代回收为辅的垃圾回收策略。
3. 为什么要用垃圾回收机制?
大多数程序在运行过程中会申请大量的内存空间,对于一些无用的内存空间如果不及时清理的话会导致内存使用殆尽(内存溢出),导致程序奔溃。
因此内存管理是一件非常重要且繁琐的事情,而垃圾回收机制能够把程序猿从繁琐的内存管理中解放出来。
4. 垃圾回收机制的原理
4.1 引用计数
x = 10 # 直接引用
print(id(x))
y = x
z = x
l = ['a.txt', 'b', x] # 间接引用
print(id(l[2]))
d = {'mmm': x} # 间接引用
print(id(d['mmm']))
x=10
l=['a.txt','b',x] # l=['a.txt'的内存地址,'b'的内存地址,10的内存地址]
x=123
print(l[2]) # 此时 x 的值变为 123 ,但l里x的值依然是10
直接引用
间接引用
引用计数减少
//值18的引用计数一旦变为0,其占用的内存地址就应该被解释器的垃圾回收机制回收
4.1 标记清除
4.1.1 循环引用——>导致内存泄漏问题
l1=[111,] #此时l1被引用一次,引用计数为1
l2=[222,] #此时l2被引用一次,引用计数为1
l1.append(l2) # l1=[值111的内存地址,l2列表的内存地址]#此时l2又被引用一次,引用计数为2
l2.append(l1) # l2=[值222的内存地址,l1列表的内存地址]#此时l1又被引用一次,引用计数为2
print(id(l1[1])) #l1引用l2
print(id(l2))
print(id(l2[1])) #l2引用l1
print(id(l1))
print(l2)
print(l1[1])
#此时,l1和l2互相引用
del l1 #l1引用次数-1
del l2 #l2引用次数-1
#此时直接引用解除关系,但间接引用还在循环引用,引用计数为1没有为0,但永远取不到值
此时两个列表的引用计数均不为0,但两个列表不再被任何其他对象关联,没有任何人可以再引用到它们
所以它俩占用内存空间应该被回收,但由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放,所以循环引用是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。
所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题
4.1.2 标记清除
堆区与栈区
在定义变量时,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。
-
① 变量名与值内存地址的关联关系存放于栈区
-
②变量值存放于堆区,内存管理回收的则是堆区的内容
标记过程
- 遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象)
- 将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
4.1.3 分代回收
分代
- 在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc对其扫描的频率会降低
分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)
-
新定义的变量,放到新生代这个等级中
假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一
-
当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代
青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间
-
接下来,青春代中的对象,也会以同样的方式被移动到老年代中
也就是等级(代)越高,被垃圾回收机制扫描的频率越低
回收
回收依然是使用引用计数作为回收的依据
分代回收的缺点
虽然分代回收可以起到提升效率的效果,但也存在一定的缺点:
例如一个变量刚刚从新生代移入青春代,该变量的绑定关系就解除了,该变量应该被回收,但青春代的扫描频率低于新生代,这就到导致了应该被回收的垃圾没有得到及时地清理。
没有十全十美的方案:
毫无疑问,如果没有分代回收,即引用计数机制一直不停地对所有变量进行全体扫描,可以更及时地清理掉垃圾占用的内存,但这种一直不停地对所有变量进行全体扫描的方式效率极低,所以我们只能将二者中和。
综上
垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案目录