Python的垃圾回收机制

blog 184

0x01 引入

Python在执行变量执行值的语法时,会去申请内存空间,去保存变量名和值。而我们的内存是有限的,我们避免某个程序占用大量空间,这就涉及到了垃圾回收机制来优化我们的内存占用。当一个变量没有用的话,那么这个变量就是垃圾。而这个垃圾我们是可以通过Python的垃圾回收机制去回收的。从而节约更多的空间。

那么什么样的变量值是垃圾呢?

在Python中,如果一个值没有指向任何变量,那么这个变量值就是一个垃圾。

在Python中,变量和值的绑定关系,大概是这样的。

代码:

a = 10 
b = 20 

在内存中的图解:

Python的垃圾回收机制

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

a = 10 
b = 20 
del a 
del b 

在内存中的图解:

Python的垃圾回收机制

此时值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 

在内存中的关系图:

Python的垃圾回收机制

可以发现,我们的值10,同时指向了三个变量即变量a和变量x以及变量z。此时值10的引用计数为3。那么当我们再次定义一个变量,将其与值10绑定,那么值10此时的引用计数就为4。

引用计数减少:

代码:

a = 10 
x = a 
z = a 
del x 
del z   

del的作用就是将一个变量与其对应的值解除绑定。

内存中的图解:

Python的垃圾回收机制

那么此时值10的引用计数就为1。值10的引用计数一旦变成了0,那么该值就会被垃圾回收机制自动回收。

0x04.2 标记-清除

当一些容器对象比如(list,set,tuple,dict,class,instance)包含其他对象引用的时候,所以都可能产生循环引用。用标记清除就可以解决这个问题。

我们先了解一下,在内存中是如何存储变量和值的。Python的变量都在栈内存中进行存储,而值都在堆内存中存储。垃圾回收机制回收的是堆内存中的内容。也就是垃圾回收只会回收垃圾(值)。

下面看图:

定义了两个变量 a = 10 b = 20

Python的垃圾回收机制

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

Python的垃圾回收机制

标记/清除算法的做法是当应用程序可用的内存空间被耗尽时,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除。

# 标记 
1 简单的来讲 标记就是从栈区通过一条线连接到堆区,或者从堆区连接到其他地址,凡是被这条来自栈区的线连接到堆区都是可以被访问到的,那么我们可以判断这个值是存活的。

具体地:标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

# 清除
清除之前会在所有堆中遍历所有对象,将没有存活的对象全部清除掉。从而释放内存。

0x04.2 什么是直接引用和间接引用

直接引用是从栈区中出发直接引用到内存地址。类似于a = 10 ,这样被称为直接应用。

而间接引用则是从栈区中出发,引用到内存地址之后,再从内存地址中,引用到别的内存地址。类似于 a = 10 b = [1,2,10]。也就是将我们的引用值先赋值给a,之后又把它放到一个容器对象(list,set,tuple等等)中又赋值给了b

以下为图解:

Python的垃圾回收机制

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

如下图:

Python的垃圾回收机制

这样在启用标记清除算法时,发现栈区内不再有a与b(只剩下堆区内二者的引用),于是列表都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。

0x04.3 分代回收

引用:

基于引用计数的回收机制,每次都回收内存,每次都需要将堆区的所有对象遍历以便,看看有没有垃圾。这是非常消耗时间的。与是引入了分代回收去提高效率。

分代:

分代回收的核心思想:在经过多次扫描之后,有个别值都没有被回收,那么Python的GC机制就会认为,这几个是常用的值。有用的值,不是垃圾。gc扫描的时候,会对这些变量值扫描频率降低。从而缩减遍历的时间。

这就好比:

交作业,假如说某个班级有100多人,而我们的课代表每次检查作业(遍历堆区)的时候需要检查100多个人的作业是否按时完成(是否是垃圾),那么这个还是比较浪费时间的,于是课代表比较聪明,它发现有大部分同学每次的作业都按时完成(变量值都有用不是垃圾),于是它决定一周检查一次他们的作业(降低扫描频次)。课代表(Python的Gc机制)觉得他们是可信度比较高的。

收:

回收依然是使用引用计数作为回收的依据

虽然,分代回收的思想是好的,但是遇到好学生突然不叫作业(常用的变量突然被删除)那么这个同学(这个变量值)就成了漏网之鱼。

声明:本文章所整理学习的知识全部由Egon老师传授

Egon老师知乎首页:https://www.zhihu.com/people/xiaoyuanqujing

分享