学无止境

少年辛苦终身事,莫向光阴惰寸功。——唐·杜荀鹤《题弟侄书堂》


python:垃圾回收

垃圾回收


  • GC(Garbage collection)垃圾回收
  • python采用的是引用计数机制为主,标记-2清楚和分代收集(隔代回收,分代回收)两种机制 为辅的策略

引用计数机制的优点:

  • 简单

  • 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。 引用计数机制的缺点:

  • • 维护引用计数消耗资源

  • • 循环引用,解决不了

  • 对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集) 引用计数的缺陷是循环引用的问题

  • import sys

  • sys.getre

  • fcount(对象)

  • 查看引用关系

  • import gc

  • gc.disable()//关闭垃圾回收机制

  • top 查看lunix进程

  • 画说 Ruby 与 Python 垃圾回收

  • GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务。它们

  • • 为新生成的对象分配内存

  • • 识别那些垃圾对象,并且• 从垃圾对象那回收内存。

  • 业务逻辑,算法,应该就是大脑

  • 我认为垃圾回收就是应用程序那颗跃动的心

  • 但在为新对象和变量分配内存的方面Python和Ruby是不同的。

  • 与Ruby不同,当创建对象时Python立即向操作系统请求内存。(Python实际上实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层。)

  • 此刻Ruby祭出另一McCarthy发明的算法,名曰:标记-清除

  • 首先Ruby把程序停下来,Ruby用"地球停转垃圾回收大法"

  • 之后Ruby轮询所有指针,变量和代码产生别的引用对象和其他值。

  • 同时Ruby通过自身的虚拟机遍历内部指针。标记出这些指针引用的每个对象。

  • 上图中那三个被标M的对象是程序还在使用的。在内部,Ruby实际上使用一串位值,被称为:可用位图

  • 标记-清除 vs 引用计数

  • 有许多原因使得不许多语言不像Python这样使用引用计数GC算法(引用计数不好的地方):

  • 首先,它不好实现

  • 第二点,它相对较慢,特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表。Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了最后,它不是总奏效的。引用计数不能处理环形数据结构–也就是含有循环引用的数据结构。


Python中的循环数据结构以及引用计数


  • 循环引用
  • 每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象
  • 如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。
  • 跟Ruby不同的是,Python中你可以在代码运行的时候动态定义实例变量或对象属性
  • 在Python中的–隔代回收

  • 使用隔代回收以空间换取时间
  • Python的引用计数算法不能够处理互相指向自己的对象。
  • 这就是为什么Python要引入Generational GC算法的原因!正如Ruby使用一个链表(free list)来持续追踪未使用的、自由的对象一样,Python使用一种不同的链表来持续追踪活跃的对象。
  • 而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)
  • 每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表
  • 使用标记清除-检测循环引用

  • //==标记清除:在零带不断的寻找活跃元素==
  • ,==隔代回收==
  • ==隔代回收:零带,一代,二代==
  • ==,Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。
  • Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。所以说python采用了标记清除解决了循环应用的问题。==

  • Python中的GC阈值

  • 当前分代列表的对象,gc.get_count()
  • 当前的阈值gc.get)threshold()
  • 一旦这个差异累计超过某个阈值(700,10,10),则Python的收集机制就启动了,并且触发上边所说到的零代算法
  • 随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表
  • 通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
  • 弱代假说
  • 来看看代垃圾回收算法的核心行为:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个来的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。
  • 为什么要这么做?这种算法的根源来自于弱代假说(weak generational hypothesis)。
  • 这个假说由两个观点构成:首先是年亲的对象通常死得也快,而老对象则很有可能存活更长的时间
  • 通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量。

3. 垃圾回收(三)-gc模块


  • 垃圾回收机制

  • Python中的垃圾回收是以引用计数为主,标记清除和分代收集为辅。
  • 导致引用计数+1的情况

  • 1.对象被创建,例如a = “hello”
  • 2.对象被引用,例如b=a
  • 3.对象被作为参数,传入到一个函数中,例如func(a)
  • 4.对象作为一个元素,存储在容器中,例如list1=[a,a]
  • 导致引用计数-1的情况

  • • 对象的别名被显式销毁,例如del a
  • • 对象的别名被赋予新的对象,例如a=24
  • • 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
  • • 对象所在的容器被销毁,或从容器中删除对象
  • 当一个对象有多个引用的时候,并且引且有不同的名称,我们称这个对象有别名(aliase)。如果有别名的对象是可变类型的,那么对一个别名的修改就会影响到另一个
  • 查看那些辣鸡:gc.garbage 显示执行垃圾回收 gc.collect
  • • 垃圾回收后的对象会放在gc.garbage列表里面
  • gc.set_debug(gc.DEBUG_LEAK)//设置gc模块的日志,如果没有设置就没有回收显示这些信息
  • 有三种情况会触发垃圾回收

    1. 调用gc.collect(),
    1. 当gc模块的计数器达到阀值的时候。
    1. 程序退出的时候
  • gc模块常用功能解析

  • **gc模块提供一个接口给开发者设置垃圾回收的选项。**上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。
  • 常用函数

  • 、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
  • gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0代表只检查零代的对象,1代表检查零,一代的对象,2代表检查零,一,二代的对象,如果不传参数,执行一个full collection,也就是等于传2。 在python2中返回不可达(unreachable objects)对象的数目
  • gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
  • 、gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
  • gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
  • gc模块的自动垃圾回收机制

  • 必须要import
  • gc模块,并且is_enable()=True才会启动自动垃圾回收。
  • 这个机制的主要作用就是发现并处理不可达的垃圾对象。
  • 垃圾回收=垃圾检查+垃圾回收
  • gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
  • 采用隔代收集案例

  • 注意点类重写__del__的问题


  • Python 垃圾回收机制总结


  • Python中的内存管理机制的层次结构提供了4层,其中最底层则是C运行的malloc和free接口,往上的三层才是由Python实现并且维护的,第一层则是在第0层的基础之上对其提供的接口进行了统一的封装,因为每个系统都可能差异性。

  • 内存池

  • Python为了避免频繁的申请和删除内存所造成系统切换于用户态和核心态的性能问题,从而引入了内存池机制,专门用来管理小内存的申请和释放;

  • 比如小整数池

  • 内存池:一个整体的概念。

  • 垃圾回收

  • Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。


类和对象的内建属性


  • dir(xxx)
  • __ getattribute__属性访问拦截器
    
  • 访问实例属性时
    
  • 调任何属性都会调用这个方法
  • __ getattribute__(self,item)
  • item就是你当时访问的属名
  • 不能调用自己的__ getattribute__的方法,会产生递归
  • 方法和属性都是收到getattribute的拦截

内建函数(Build-in Function)


  • python解释器启动后默认加载的属性和函数,这些函数称之为内建函数
  • range

  • range(start, stop[, step]) -> list of integers
  • start:计数从start开始。默认是从0开始。例如range(5)等价于range(0, 5);
  • stop:到stop结束,但不包括stop.例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
  • step:每次跳跃的间距,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
  • python2中range返回列表,python3中range返回一个迭代值。如果想得到列表,可通过list函数
  • map函数

  • map函数会根据提供的函数对指定序列做映射
  • map(function, sequence[, sequence,.]) -> list
    
  • • function:是一个函数
  • • sequence:是一个或多个序列,取决于function需要几个参数
  • • 返回值是一个list
  • result = map(lambda x:x*x, [1,2,3,4,5])#结果为:[1, 4, 9]
  • lanbda后面是表达式
  • 如果两个列表长度不一致,安装短的列表长度进行计算
  • map返回的是一个list只能用list进行查看
  • filter函数

  • python3返回的是生产器
  • filter函数会对指定序列执行过滤操作
  • filter(function or None, sequence) -> list, tuple, or string
  • • function:接受一个参数,返回布尔值True或False
  • • sequence:序列可以是str,tuple,list如果是tuple str则返回相同的类型否则返回List
  • sorted函数-排序

  • sorted(iterable, cmp=None, key=None, reverse=False) –> new sorted list
  • sorted(iterable)默认升序
  • reverse=1or False
  • functools模块

  • import functools
      dir(functools)
    
  • partial函数(偏函数)

  • 把⼀个函数的某些参数设置默认值,返回⼀个新的函数,调⽤这个新函数会 更简单
  • wraps函数

  • __ name__获取函数名
  • __ doc__获取函数文档说明"“211"”"
  • @functools.wraps(参数名)
  • 作用,让外界查看的时候能够保持装饰前后一致