3-python-gc垃圾回收[Python常见问题]

垃圾回收

当我们在定义变量的时候,变量会在内存中申请空间用来存储内存的值,当变量不在被引用的时候 内存地址需要释放,内存的释放机制,可以理解为垃圾回收机制。

如果内存的值一直不被释放,会存在内存泄漏的风险

垃圾回收机制(简称GC)是Python解释器自带一种机制,python的Cpython解释器会帮我们实现垃圾回收,

基础知识

在定义变量的时候,变量名 于变量的值是分开存储的,分别对应内存中的两块区域:堆区与栈区

栈区:存放的是变量名与值内存地址的关联关系 存的是对应关系

堆区:变量值存放于堆区,内存管理回收的则是堆区的内容

x=10 x与10的对应关系存在栈区1-1 10这个值 存在堆区1

y=20 y 与 20的对应关系存在与站区 2-2 20这个值存在与堆区2

image-20200912074959946

当我们执行 x=y的时候

x栈区的对应关系变成了 2-2 其它的地方不变,

image-20200912075055069

image-20200912075224608

直接引用于间接的引用

直接引用:指的是从栈区出发直接引用到的内存地址。 也就是通过栈区的变量名可以直接找到堆区值的引用

间接引用: 指的是从栈区出发引用到堆区后,再通过进一步引用才能到达的内存地址。 无法通过栈区的名字一步实现找到堆区的值

直接引用举例

x=10

y=x

y就是直接引用的x的值

间接引用举例

list1 = [“24”, “b”, “luck”]

list2=[“ab”,”ac”,list1]

列表list2对list1是直接引用 但list2对list2中的值是间接引用

list2要取出list1 中24这个值 需要list2的栈区需要先找到对应的list1的站区,然后在通过list1的栈区找到对应的list[0]的值

list1 = ["24", "b", "luck"]
print(id(list1))
print(id(list1[0]))
list2 = ["ab", "ac", list1]
print(id(list2[2]))
print(list2[2][0])
print(id(list2[2][0]))

2365435328832 list1的id
2365405395696 24值的id
2365435328832 list 2中list1的id
24
2365405395696 list2中list1中24的id

课件list2中存的list1 是存了list1的栈区的内存地址

Python的GC模块 内存回收机制主要运用下面三个计数

引用计数(reference counting)来跟踪和回收垃圾。

在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,

分代回收(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率

下面分别讲一下三种计数的实现

1. 引用计数

引用计数其实就是指的 变量的值被关联的次数,也就是有多少个直接或者间接指向改值的内存地址,计算个数 如果这个数为0 则回收改值的内存空间

举例

a=10 10 这个值的内存空间 有一个计数了

x=a 在内存中x这个栈区指向的堆区也是 10的内存空间 计数+1 变成2 了

del a

del x 当两个变量名都删除后 x a 的栈区没有了 10的内存空间的引用计数变为0 10的内存空间被回收。

2. 标记-清除

标记清除是为了解决引用计数的漏洞

下面例子

x= [“xxx”]

y=[“yyy”]

x.append(y) 列表y的引用计数变为2

y.append(x) 列表x的引用计数变为2

# x与y之间有相互引用
# x = ["xxx"的内存地址,列表2的内存地址]
# y = ["yyy"的内存地址,列表1的内存地址]

del x 列表x的引用计数变为1 来自y的引用

del y 列表的y引用计数变为1 列表x的引用

现在没有变量名指向这两个内存地址的值 但是这两个内存地址因为彼此的相互引用,引用计数不为0 通过引用计数的方式是无法被清除的.

循环引用会导致 内存变量无法被访问到,同时通过标记清除的方式无法清除,造成大量无用的变量占用内存。

标记清除实现的算法:当应用程序内存耗尽的时候,停止整个程序,进行两项操作,一个是标记,一个操作是清除。

标记: 我们知道当一个变量存在内存中的时候,会产生栈区和堆区 栈区存的是内存地址和内存值的对应关系

而堆区存的是真正的变量的值,当我访问堆区内容的时候,只能通过栈区直接或者间接的访问到堆区的内容

标记的做法是 凡事所有从栈区出发,可以访问到的堆区内容的值标记 不论是栈区–>堆区 还是 栈区—->**—->堆区,只要是栈区出发,最终可以访问到的堆区的值都标记,栈区出发 无法访问到的值则标记为需要清除的值,

因为我们无法绕过栈区 直接访问到堆区的值。

清除:遍历堆区中的所有对象值,将没有标记的对象全部清除

这样就解决了变量之间循环引用造成的内存泄漏问题

问题: 我们知道标记-清除是要遍历整个程序内存中所有变量的值,整个遍历的过程会非常消耗资源的,同时所有的值都变量一次,效率上也不高,所以下面引用了分代回收机制 “ 空间换时间” 牺牲一部分内存空间换来更高的执行效率、分代回收机制

3.分代回收

存活时间:遍历一边内存的值 存活没有被清除的记为存活时间,多次扫描 仍然存活的 我们称为老年代(变量创建的时间早,但是多次扫描仍然在使用)

那有老年代 就是 青年代 青春代 新生代 以此创建时间越来越短。不同的”代“ 扫描的权重值不同,年轻的权重值高

把不同的变量分代以后,年轻的变量扫描次数多 年老的扫描次数少,例如 新生代 半小时一次,青春带 2小时一次

到了 老年代 可以一天扫描一次。

通过分代回收机制,减少 遍历的次数和个数,提高 标记-清除的效率。

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 3-python-gc垃圾回收