05.垃圾回收机制
考试10分钟:
1.什么是变量?为何要有变量?
变量就是可以变化的量,量是指事物的状态,比如人的年龄,性别,游戏角色的等级。
为了让计算机能够像人一样去记忆某种事物的状态,并且状态是可以发生变化的
2.变量的三个组成部分是什么?每部分的作用是什么?
变量名:指向赋值符号右侧内容的内存地址,用来访问赋值符号右侧的值
赋值符号:将变量值的内存地址绑定给变量名
变量值:代表记录事物的状态
3.变量名的命名原则、规范、风格
原则:变量名的命名应该见名知义
规范:1.字母数字下划线的组合
2.不能以数字开头
3.不能用python关键字作为变量名
风格:纯小写+下划线 :age_of_alex
驼峰体:AgeOfAlex
4.变量值的三个特征是什么?
id
type
值
5.is与==的区别
is用来比较左右两个值的身份(id)是否相等
==:用来比较左右两个值是否相等
6.id相同,值是否相等
id相同,值一定相等
7.id不同值是否可以相同
id不同,值有可能相等
8.用变量的定义说明int、float、str、list、dict、bool类型用于记录何种状态,每种类型至少写出三个例子,如下所示:
#int类型 age = 10 level = 3 year = 2021 #float类型 salary = 3.4 height = 1.7 weight = 50.8 #str类型 name = 'egon' gender = 'male' coutry = 'China' #list类型 info = ['egon',18,'male'] s1 = [1,3,5,7,9] s2 = ['lili','zhangsan','lisi'] #dict类型 info = {'name':'egon', 'age':18, 'gender':'male'} #bool类型 is_ok = True is_ok = Flase
今日内容
1.垃圾回收机制详解(*****)
引用计数
标记清除
分代回收
2.与用户交互
接收用户输入
python3 input
python2 input、raw_input
格式化输出
%
str.form
3.基本运算符
算术运算符
赋值运算符
=
增量赋值
链式赋值
交叉赋值
解压赋值
比较运算符
逻辑运算符
not and or
优先级:not > and > or
了解:短路运算
成员运算符
in
身份运算符
is
3.流程控制之if判断
if 条件:
代码块
elif 条件:
代码块
......
else:
代码块
今日内容详解
一、垃圾回收机制
引入:当我门在程序中定义一个变量的时候,python解释器会申请一个内存空间来存放变量的值,但是内存的空间不是无限大的,是有限度的。这就会涉及到变量值占用的内存空间的回收问题。
当一个变量值没有用了(简称垃圾),就应该将其所占用的内存空间给回收掉,那么问题来了,什么样的变量值是没有用了呢?
先来说一下,我们定义变量是为了将变量值存储起来,但是存不是目的,目的是为了以后取出来用,而要想取变量值,你就得通过变量值当时绑定的直接引用或者间接引用。当一个值不再绑定任何变量值的时候,这个变量值就是没有用的,就应该被当成一个垃圾回收。
但是内存空间的申请和回收都是非常消耗资源的,并且存在很大的危险性,稍有不慎就有可能引发内存溢出的问题,因为CPython解释器提供了自动的垃圾回收机制来帮我们解决了这件事情。
二、什么是垃圾回收机制
垃圾回收机制(GC:Garbage Collection)是Python解释器自带的一种机制,专门用来回收不可用的变量所占用的内存空间
三、为何要有垃圾回收机制
程序运行过程中会申请大量的内存,一段时间之后,会有很多没有用的内存空间,如果不及时清理,会导致内存使用殆尽(内存溢出),程序崩溃。因此内存管理是一件重要而且复杂的事情,而Python解释器自带的垃圾回收机制,会将程序员从这种复杂的工作中解放出来
四、垃圾回收机制原理分析
Python的GC机制,主要运用了“引用计数(reference counting)”来跟踪和回收垃圾,在“引用计数”的基础上,还可以通过“标记-清除(mark and sweep)”解决容器对象可能产生的循环引用问题,并且通过“分代回收(generation collection)”以空间换取时间的方式来进一步提高垃圾回收的效率
4.1 引用计数 reference counting
引用计数就是:变量值被关联的变量名的次数
如:age = 18
变量值18被关联了一个变量名age,称之为引用计数为1
引用计数增加:
age = 18(此时,值18的引用计数为1)
m = age(把age的内存地址给了m,此时,m,age都关联了18,所以变量值18的引用计数变为2)
引用计数减少:
age = 10(名字age先与值18解除关联,然后与10再建立了关联,此时值18的引用计数变成了1)
del m(del的意思是解除变量名m与值18的关联,此时,值18的引用计数变成了0)
值18的引用计数一旦变为0,其占用的内存地址就应该被解释器的垃圾回收机制回收
4.2 循环引用
引用计数机制的执行,会带来明显的效率问题,因为:变量值被关联次数的增加或者减少,都会引发这一机制的执行。但是效率问题还仅仅是一方面
引用计数还存在一个致命的弱点,那就是循环引用。
l1 = [111,222] l2 = [333,444] l1.append(l2) l2.append(l1) print(l1) #[111, 222, [333, 444, [...]]] print(l2) #[333, 444, [111, 222, [...]]]
循环引用导致:某个值不会被任何名字关联,但是值的引用计数不会变为0,所以,这个值应该被回收,但是不能回收。当我们执行如下操作时:
del l1 del l2
此时,l1和列表本身的引用解除了,l2和列表本身的引用也解除了,但是l1这个列表还被l2引用着,同样的l2还被l1引用着,这两个列表的直接引用都解除了,但是间接引用还在,所以引用计数还没有变为0
因此他们占用的内存空间永远不会被回收,所以这种循环引用是致命的。python为此引入了“标记清除”和“分代回收”来解决这一问题。
4.3 标记清除
容器对象(比如:list,set,dict,class,instance)都可以包含对其他对象的引用,所以都有可能产生循环引用。而标记清除就是为了解决循环引用的问题。
内存中有两块空间:栈区和堆区,在定义变量的时候,变量名与值内存地址的关联关系被放在栈区,变量值本身存放于堆区,内存管理回收的是堆区的内容。
例子:定义两个变量
x = 10
y = 20
他们在内存中的存储关系如下:
当我们执行x = y的时候,内存中栈区与堆区的变化如下
标记清除算法的工作原理是:当应用程序的可用的内存空间消耗殆尽的时候,就会停止整个程序,然后进行两项工作,一项是标记,一项是清除
""" 标记:相当于从栈区出发的一条线,“连接”到堆区,再由堆区间接“连接”到其他地址,凡是能被这条从栈区出发的线访问的,都被标记为存活 形象的讲,讲栈区比喻成树根,从树根出发能够访问到的树枝或者树叶,都可以存活,如果从树根出发,不能被访问到的树枝或者树叶, 不会被标记存活,也就是有根之叶当活,无根之叶当死 清除:凡是没有被标记为存活的对象,会被全部清除掉 """
4.3.1 直接引用和间接引用
当我们同时执行del l1和del l2的时候,会清理栈区中的l1与l2的内容
以上这种情况,启用标记清除算法的时候,发现栈区内不再有l1和l2,于是列表1和列表2都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄露问题
4.4 分代回收
基于引用计数的回收机制,每次内存回收,都需要把所有对象的引用计数遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率,分代回收采用的是“空间换时间”的概念
分代:
分代回收的核心是:经历过多次扫描,依然没有被回收的变量,gc机制就会认为,这个变量是常用变量,就会降低对该变量的扫描频率。
分代指的是根据存活时间来为变量划分不同的等级
新定义的变量,放到新生代中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重+1,当权重大于某个设定值的时候,会将它移动到青春代,青春代的gc扫描
频率低于新生代,假设每5分钟扫描青春代一次,这样每次gc需要扫描的变量总个数就减少了,节省了扫描时间,接下来,青春代中的变量也会同样的移动到老年代。也就是等级越高,被垃圾回收
机制扫描的频率越低
虽然分代回收提升了效率,但是也存在一定的问题:
例如一个新定义的变量,刚刚从新生代移到青春代,该变量的绑定关系就被解除了,该变量应该被回收,但是青春代的扫描频率低于新生代,所以该变量的回收会被延迟