引入
当解释器再执行到定义变量的语法时,会申请内存空间来存放变量的值,但是内存的容量时有限的,当你不需要该变量值时它仍然会占用你的内存空间,这就设计到了‘垃圾’的回收问题,当一个变量值没有用后我们应该将其回收掉以释放内存。
单从逻辑层面分析,我们定义变量的值存起来是为了方便以后的取用,而取得一个存储的变量值就需要与其绑定的名称来直接或间接调用该值,所以当一个值没有再绑定任何索引时,我们就无法访问到这个存储的变量值了,此时这个存储的变量值也就变成了‘垃圾’,此时我们就应该想办法回收它。
首先需要说的是,内存空间的申请和回收都是很耗费精力的事情,而且存在很大的危险性,稍有不慎就可能导致内存溢出,好在Cpython解释器提供了自动的垃圾回收机制来解决这件事。
什么是垃圾回收机制?
垃圾回收机制,简称GC,是Python自带的一种机,专门用来回收不可用变量值所占用的内存空间。
为什么要使用垃圾回收机制?
在程序运行过程中会申请大量的内存空间,而对于一些无用的内存空间如果不及时清理就会导致内存溢出,即内存消耗殆尽,导致程序的崩溃,而内存管理是一件重要且繁杂的事情,我们使用垃圾回收机制的目的就是为了把程序员从繁杂的内存管理中解放出来。
理解垃圾回收机制(GC)
堆区与栈区
堆区和栈区是计算机内存中的两种不同的分配方式
栈区
栈区是由编译器自动分配的内存空间,用于存储函数的参数值,局部变量的值等。栈区是顺序存储数据结构,遵循先进后出(LIFO)原则。当一个函数调用结束后,它的栈帧就会被自动释放,栈顶指针回到它的上一个位置
堆区
堆区也是系统提供的内存空间,用于存储程序运行时动态分配的内存空间。堆区没有固定的大小,并且空间的申请和释放都是通过程序员手动控制的。当我们使用C++中的new操作符来创建对象时,对象就会被分配在堆区。需要注意的是,当我们不再需要这个对象时,需要手动使用delete操作符来释放其所占用的内存空间,否则会造成内存泄漏
总体来说,栈区的内存是自动管理的,操作简单,速度快,但是空间有限;而堆区的内存是手动管理的,灵活性高,但是容易产生内存泄漏等问题。在内存分配时,需要根据实际需要进行权衡
直接引用与间接引用
直接引用指的是从栈区出发直接引用到的内存地址。
间接引用指的是从栈区出发引用到堆区后,再通过进一步引用才能到达的内存地址。
图解见下:
垃圾回收机制原理分析 Python的GC模块主要运用的‘引用计数(reference counting)’来跟踪和回收垃圾。在引用计数的基础上,还可以通过‘标记清除(mark and sweep)’解决容器对象可能产生的循环引用的问题,并且通过‘分代回收(generation coliection)’以空间换取时间的方式来进一步提高垃圾回收的效率。 引用计数 变量值被变量名关联的次数,当这个值变为0,存储该垃圾的内存地址就会被垃圾回收机制回收。 引用计数的缺陷一:循环引用(也称交叉引用)问题
我们使两个数据相互引用,然后断开变量名与内存地址的关联
如上图可见,此时两个内存地址的引用技术均不为0,但不再被其他对象所关联,无人引用我们应该回收内存地址,但是由于他们的相互引用,内存无法被释放,所以Python引入了‘标记清除’和‘分代回收’来解决其循环引用的问题,并提升其效率。
解决循环引用问题:标记清除
容器对象都可以包含对其他对象的引用,都可能导致循环引用,标记清除就是为了解决这一问题。标记清除算法的做法是当应用程序可用的内存空间被耗尽时停止整个程序,然后进行标记和清除两项工作
#1、标记
标记的过程就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
#2、清除
清除的过程就是将遍历堆中所有的对象,将没有标记的对象全部清除掉。
在启用标记清除算法时,由于l1和l2已经被del,这时两者的内存地址就会被清理掉。
引用计数的缺陷二:效率问题
基于引用计数的回收机制,每次回收内存都需要把所有对象的引用计数遍历一边,这是非常浪费时间的,于是就引入了分代回收(空间换时间)用以提高回收效率
解决效率问题:分代回收
分代:
在多次扫描后都没有被回收的变量,gc就会认为这是常用变量并降低对其的扫描频率,也就是说哦分代是根据存活时间来为变量划分不同等级(也就是不同的代)
新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。
回收:仍然是基于引用计数的回收方式
分代回收的缺陷:
假如一个变量刚刚从新生代移入青春代,该变量的绑定关系就被断了,该变量按理来说应该被回收,但青春代的扫描频率低于新生代,这就到导致了应该被回收的垃圾没有得到及时地清理而继续占用内存空间。
但是毫无疑问,如果没有分代回收而使用引用计数机制一直不停地对所有变量进行全体扫描,固然可以更及时地清理掉垃圾占用的内存,但这种一直对所有变量进行全体扫描的方式效率极低,所有我们只能牺牲部分的空间来提升效率,这是一种折中的方法,是效率和内存空间的中和。
综上,垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案
标签:变量,引用,内存空间,回收,内存,聊聊,垃圾 From: https://www.cnblogs.com/Cai-jia-hui/p/17400797.html