python 垃圾,

  python 垃圾,

  Yyds干货库存

  前戏解释器在执行定义变量的语法时,会申请内存空间来存储变量的值,内存的容量是有限的,这就涉及到变量值占用的内存空间的回收问题。当一个变量值无用时(简称垃圾),就应该回收。什么样的变量值没用?

  因为变量名是访问变量值的唯一方式,当一个变量值不再与任何变量名相关联时,我们就不能再访问变量值了。变量值没有用,应该作为垃圾回收处理。毫无疑问,内存空间的申请和回收是一件非常耗费精力的事情,而且存在很大的危险。稍有不慎就可能导致内存溢出。幸运的是,Cpython解释器提供了自动垃圾收集机制来帮助我们解决这个问题。

  什么是垃圾收集机制?垃圾收集机制(简称GC)是Python解释器搭建的机器,专门用来回收不可用变量值占用的内存空间。

  为什么有垃圾收集机制的程序在运行过程中会申请大量的内存空间,一些无用的内存空间如果不及时清理就会被耗尽(内存溢出),导致程序崩溃。所以管理内存是一件重要而复杂的事情,python解释器自带的垃圾收集机制将程序员从复杂的内存管理中解放出来。

  垃圾收集机制原理分析Python的GC模块主要利用“引用计数”来跟踪和收集垃圾。在引用计数的基础上,还可以通过“标记和清扫”解决容器对象的循环引用问题,通过“代收集”以空间换时间,进一步提高垃圾收集的效率。

  Count引用计数是变量值与变量名关联的次数。

  例如name=cjk

  变量值cjk与变量名相关联,变量名称为引用计数1。

  参考计数增加:

  Name=cjk(此时,变量值18的引用计数为1)

  m=name(name的内存地址给了m,此时所有的名字都与cjk关联,所以变量cjk的引用计数为2)

  参考计数减少:

  name=xzml(name name先与cjk解除关联,再与3建立关联,变量cjk的引用计数为1)

  Del m(del表示将变量名X与变量值cjk解除关联,此时变量cjk的引用计数为0)

  一旦cjk的引用计数变为0,CJK占用的内存地址就要被解释器的垃圾收集机制回收。

  引用扩展读取变量的值关联次数的增加或减少会导致引用计数机制的执行(增加或减少值的引用计数),存在明显的效率问题。

  如果说执行效率只是引用计数机制的一个弱点,那么很遗憾,引用计数机制还有一个致命的弱点,就是循环引用(也叫交叉引用)。

  #如下,我们定义了两个列表,简称为列表1和列表2。变量名l1指向列表1,变量名l2指向列表2。

  L1=[xxx] #清单1被引用一次,清单1的引用计数变成1。

  L2=[yyy] #清单2被引用一次,清单2的引用计数变成1。

  L1.append(l2) #将清单2作为第二个元素追加到L1,清单2的引用计数变为2。

  L2.append(l1) #将list 1追加到L2中作为第二个元素,list 1的引用计数变为2。

  # l1和l2相互参照。

  # l1=[xxx 的xxx内存地址,清单2的内存地址]

  # l2=[yyy 的yyy存储器地址,列表1的存储器地址]

  腰神经2

  [xxx ,[yyy ,[.]]]

  l2

  [yyy ,[xxx ,[.]]]

  l1[1][1][0]

  xxx

  循环引用会导致:值不再被任何名称关联,但值的引用计数不会为0,应该回收但不回收。那是什么意思?想象一下,请看下面的操作。

  Del l1 #清单1的引用计数减1,清单1的引用计数变成1。

  Del l2 #清单2的引用计数减1,清单2的引用计数变成1。

  此时,只剩下列表1和列表2之间的交叉引用。这两个列表的引用计数不为零,但是这两个列表不再与任何其他对象相关联,没有人可以再次引用它们,所以它们所占用的内存空间应该被回收。但是由于交叉引用的存在,每个对象的引用计数都不为零,所以这些对象占用的内存永远不会被释放,所以循环引用是致命的,这和手动管理内存造成的内存泄漏没有什么区别。于是Python引入了“标记-清除”和“世代回收”,分别解决循环引用和引用计数效率低的问题。

  标记-清除容器对象(如:list、set、dict、class、instance)可以包含对其他对象的引用,因此可能会产生循环引用。“清除标记”计数是为了解决循环引用的问题。

  在理解标签清除算法之前,我们需要明确内存中有两个区域:堆区和栈区。定义变量时,变量名和值内存地址的关系存储在堆栈区,变量值存储在堆区,内存管理回收堆区的内容,如下图所示。

  定义了两个变量x=10和y=20。

  当我们执行x=y时,内存中的堆栈区域和堆区域变化如下

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

  #1,标记

  通俗地说,标记的过程相当于从栈区开始一行,将其“连接”到堆区,再从堆区间接“连接”到其他地址。通过从堆栈区开始的这条线连接到内存空间的任何人都是可访问的,并将被标记为活动的。

  具体来说,标记的过程其实就是遍历所有的GC根对象(栈区的所有内容或线程都可以作为GC根对象),然后把所有可以被GC根对象直接或间接访问的对象都标记为活对象,剩下的都是非活对象,要清除掉。

  #2,安全

  清除的过程将遍历堆中的所有对象,所有没有标记的幸存对象都将被清除。

  世代回收背景:

  基于引用计数的回收机制,每次回收内存都需要遍历所有对象的引用计数,非常耗时。因此,为了提高回收效率,引入了代际回收,并在代际回收中采用了“空间换时间”的策略。

  世代:

  生成回收的核心思想是:如果多次扫描后没有回收的变量,gc机制会认为该变量是公共变量,gc扫描的频率会降低。具体实施原则如下:

  世代是指将变量按照生存时间分为不同的等级(即不同的世代)。

  假设每1分钟扫描一次新一代,新定义的变量被放在新一代级别中。如果发现变量仍然被引用,则对象的权重(权重本质上是整数)加1。当变量的权重大于某个设定值(假设为3)时,将移至更高一级的青年代。青年一代的gc扫描频率低于新生代(扫描时间间隔更长),假设青年一代每5分钟扫描一次。这样,每个gc中需要扫描的变量总数就会减少,从而节省了总的扫描时间。接下来,青年一代中的对象将以同样的方式移动到老年。即级别(代)越高,被垃圾收集机制扫描的频率越低。

  回收:

  回收仍然基于引用计数。

  虽然分代回收可以提高效率,但它也有一些缺点:

  比如,一个变量从新代移到青年代,该变量的绑定关系就被释放了,该变量应该被回收,但青年代的扫描频率低于新一代,所以该变量的回收会被延迟。

  转载请联系作者获得授权,否则将追究法律责任。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: