03-03 python语法入门之基本运算符
一、什么是垃圾回收机制?
垃圾回收机制(简称GC),是python解释器自带的一种机制,专门用来回收不可用得变量值所占用得内存空间。
二、为什么要有垃圾回收机制?
程序得运行过程中会申请大量的内存空间,而对于一些无用的内存空间如果不及时清理的话,会导致内存使用殆尽(也就是内存溢出),从而导致程序奔溃。因为管理内存是一件复杂且繁琐的事情,而python解释器自带的垃圾回收机制把程序员从复杂的内存管理中解放出来。
三、垃圾回收机制原理分析
1、什么是引用计数?
运用了“引用计数”(reference counting)来跟踪和回收垃圾。
引用计数就是变量值被变量名关联的次数。
1.1 直接引用
通过变量名直接引用
x = 10 # 值10直接引用:1次
y = x # 值10直接引用:2次
z = y # 值10直接引用:3次
print(id(x)) # 140715024107456
print(id(y)) # 140715024107456
print(id(z)) # 140715024107456
1.2 间接引用
主要争对容器类型
c = 10
li = ['a', 'b', c] # 列表相当于['a', 'b', 10],值10被间接引用:1次
print(id(c)) # 140715024107456
print(id(li[-1])) # 140715024107456, 值10被间接引用:2次
dic = {'key': c} # 值10被间接引用:3次
print(id(dic['key'])) # 140715024107456, 值10被间接引用:4次
1.3 由引用关系刨析列表取值的底层原理过程
我们知道列表是由所以取值,且列表能存多个任意类型的值,当我们通过列表取值时对应的正真的就是屏幕上我们所看到的值吗?我们来看图分析:
更具上图分析,列表的取值操作,我们是由索引对应值的内存地址,再通过这个内存地址找到对应值所在内存中的正真位置,列表当中相当于存放了内存地址的目录,通过这个目录我们访问到正真值所在的位置。
1.4 总结
- 只要是访问到你那个值得方式,就相当于增加一次引用。
- 只要是直接通过变量名访问值得方式,都是直接引用。
- 间接引用一般是通过容器类型来进行对值得访问,例如:列表,字典类型等
- 列表中的值并不是正真的值,而是通过内存地址映射到值得位置。
- 直接引用从栈区出发,间接引用只作用堆区。直接引用是栈区到堆区的桥梁,间接引用只存在堆区(容器类型它对于的元素的访问都是间接引用)
2、引用计数扩展阅读
2.1 标记清除(栈区、堆区)
通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用(也交交叉引用)的问题。
循环引用时,值不再被任何名字关联,但是值的引用计数并不会为0,因该被回收,但不能被回收,这个时候就需要python的标记清除功能,清除没有被直接引用的值。
标记清除程序并不是时时检测的,只有内存快满了,python程序才会启动标记清除程序,扫描栈区,如果谁身上没有直接引用的标识,它将根据对应内存地址去清除堆区的正真值占用的内存。
栈区:存放的是变量名与变量值的内存地址的映射关系。
堆区:存放值的正真位置。
详细内容地址:https://zhuanlan.zhihu.com/p/108683483
li1 = [10]
li2 = [20]
li1.append(li2) # li1 = [值10对应得内存地址, 列表2对应得内存地址],此时列表li1得引用计数为2
li2.append(li1) # li2 = [值20对应得内存地址,列表1对应额内存地址],此时列表li2得引用计数为2
del li1
del li2
# 通过del解除变量名li1与值[10]得绑定关系,变量名li2与值[20]的绑定关系,虽然解绑了直接引用,此时li1与li2得间接引用关系没有断,任然互相关联着,也就是说li1与li2值当前引用计数为1,但是它们并不能被访问到了,这就导致了它们一直占用了这块内存空间,这对于计算机来说是致命得。
# 总结:循环引用时,值不再被任何名字关联,但是值的引用计数并不会为0,因该被回收,但不能被回收。
总结:标记清除是用来解决循环引用带来得内存泄漏问题。
2.2 分代回收
- 通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。
- 详细内容地址:https://zhuanlan.zhihu.com/p/108683483
总结:用来降低引用计数的扫描频率,提升垃圾回收的效率。
3、总结
GC技术的核心:引用计数, 负责跟踪回收垃圾.
围绕引用计数拓展:
1. 标记清除: 解决容器类型存在循环引用的问题。
执行前提: 当应用程序可用内存空间快被耗尽.
标记: 有根之人当活, 无根之人当死. 根指的栈区, 也就是说可以通过栈区间接或者直接可以访问到堆区的对象的数据会被保留. 否则执行清除.
清除: 遍历堆区中所有对象, 将没有标记的对象全部清除.
2. 分代回收: 解决引用计数回收垃圾时需遍历内存中所有对象的效率
根据存活时间划分扫描频率. 刚来的数据权重最低, 扫描频率最高. 数据存活时间越长权重越高, 扫描频率越低.
直接引用: 从栈区出发到堆区的引用.
间接引用: 从只在堆区中的对象之间的互相引用.(提示: 容器类型访问值时)
栈区 & 堆区:
栈区:存放的是变量名与变量值的内存地址的映射关系。 变量名 --> 内存地址。
堆区:存放的是变量值所在的内存地址。
注意:只站在变量的名的角度去谈一件事情. 变量名的赋值(x=y),还有变量名的传参(print(x)),传递的都是栈区的数据,而且栈区的数据是变量名与内存地址的对应关系,或者说是内存地址对值的引用.
强调!!!:变量的赋值,并不是值得赋值,而是内存地址的赋值