0x01 引入
Python在执行变量执行值的语法时,会去申请内存空间,去保存变量名和值。而我们的内存是有限的,我们避免某个程序占用大量空间,这就涉及到了垃圾回收机制来优化我们的内存占用。当一个变量没有用的话,那么这个变量就是垃圾。而这个垃圾我们是可以通过Python的垃圾回收机制去回收的。从而节约更多的空间。
那么什么样的变量值是垃圾呢?
在Python中,如果一个值没有指向任何变量,那么这个变量值就是一个垃圾。
在Python中,变量和值的绑定关系,大概是这样的。
代码:
a = 10
b = 20
在内存中的图解:

此时,变量a和10的内存地址绑定,而变量b和值20进行绑定,那么他们现在都不是垃圾。当我们使用del解除某个变量的绑定时,他们此时就会变成垃圾。
a = 10
b = 20
del a
del b
在内存中的图解:

此时值10和20,都没有被任何变量绑定,那么他们就是垃圾。没有用的,此时Python的垃圾回收机制会将其回收。
0x02 什么是垃圾回收机制?
垃圾回收机制(简称GC)是Python解释器自带一种禁止,专门用来回收不可用的变量值所占用的内存空间,进而节省空间。
0x03 为什么要有垃圾回收机制?
Python的垃圾回收机制会自动的回收一些没用的变量值,从而节省空间,如果一个程序占用了大量内存甚至占满整个内存,那么此时这个电脑是出于满载状态的,满载的计算机会致使程序的崩溃,所以在开发的时候,自动回收哪些没有用的变量值,是很有必要的。从而节省内存空间。
0x04 垃圾回收机制的三大模块?
Python的垃圾回收机制主要应用了“引用计数”(reference counting)来跟踪和回收垃圾,在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。
0x04.1 什么是引用计数?
引用计数表示一个值被绑定变量的次数。
引用计数增加:
在内存中,变量绑定都是一个值对应的内存地址。而我们将一个值绑定为三个变量,那么此时这个值的引用计数就为3。
a = 10
x = a
z = a
在内存中的关系图:

可以发现,我们的值10,同时指向了三个变量即变量a和变量x以及变量z。此时值10的引用计数为3。那么当我们再次定义一个变量,将其与值10绑定,那么值10此时的引用计数就为4。
引用计数减少:
代码:
a = 10
x = a
z = a
del x
del z
del的作用就是将一个变量与其对应的值解除绑定。
内存中的图解:

那么此时值10的引用计数就为1。值10的引用计数一旦变成了0,那么该值就会被垃圾回收机制自动回收。
0x04.2 标记-清除
当一些容器对象比如(list,set,tuple,dict,class,instance)包含其他对象引用的时候,所以都可能产生循环引用。用标记清除就可以解决这个问题。
我们先了解一下,在内存中是如何存储变量和值的。Python的变量都在栈内存中进行存储,而值都在堆内存中存储。垃圾回收机制回收的是堆内存中的内容。也就是垃圾回收只会回收垃圾(值)。
下面看图:
定义了两个变量 a = 10 和 b = 20

当我们执行a = b时,内存中的堆与栈发生了变化

标记/清除算法的做法是当应用程序可用的内存空间被耗尽时,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除。
# 标记
1 简单的来讲 标记就是从栈区通过一条线连接到堆区,或者从堆区连接到其他地址,凡是被这条来自栈区的线连接到堆区都是可以被访问到的,那么我们可以判断这个值是存活的。
具体地:标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
# 清除
清除之前会在所有堆中遍历所有对象,将没有存活的对象全部清除掉。从而释放内存。
0x04.2 什么是直接引用和间接引用
直接引用是从栈区中出发直接引用到内存地址。类似于a = 10 ,这样被称为直接应用。
而间接引用则是从栈区中出发,引用到内存地址之后,再从内存地址中,引用到别的内存地址。类似于 a = 10 b = [1,2,10]。也就是将我们的引用值先赋值给a,之后又把它放到一个容器对象(list,set,tuple等等)中又赋值给了b
以下为图解:

当我们执行 del a 和 del b的时候,会清理变量a和变量b的内容。
如下图:

这样在启用标记清除算法时,发现栈区内不再有a与b(只剩下堆区内二者的引用),于是列表都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。
0x04.3 分代回收
引用:
基于引用计数的回收机制,每次都回收内存,每次都需要将堆区的所有对象遍历以便,看看有没有垃圾。这是非常消耗时间的。与是引入了分代回收去提高效率。
分代:
分代回收的核心思想:在经过多次扫描之后,有个别值都没有被回收,那么Python的GC机制就会认为,这几个是常用的值。有用的值,不是垃圾。gc扫描的时候,会对这些变量值扫描频率降低。从而缩减遍历的时间。
这就好比:
交作业,假如说某个班级有100多人,而我们的课代表每次检查作业(遍历堆区)的时候需要检查100多个人的作业是否按时完成(是否是垃圾),那么这个还是比较浪费时间的,于是课代表比较聪明,它发现有大部分同学每次的作业都按时完成(变量值都有用不是垃圾),于是它决定一周检查一次他们的作业(降低扫描频次)。课代表(Python的Gc机制)觉得他们是可信度比较高的。
回收:
回收依然是使用引用计数作为回收的依据
虽然,分代回收的思想是好的,但是遇到好学生突然不叫作业(常用的变量突然被删除)那么这个同学(这个变量值)就成了漏网之鱼。
声明:本文章所整理学习的知识全部由Egon老师传授。
Egon老师知乎首页:https://www.zhihu.com/people/xiaoyuanqujing
本文作者为blog,转载请注明。