Python36_垃圾回收

注意,垃圾回收机制的测试建议以交互模式测试

ps:对于垃圾回收机制,有的python版本默认开了,有的则默认没有开

intern机制

a2 = "hello"
a3 = "hello"
a1 = "hello"
a4 = "hello"
a5 = "hello"
print(id(a1),id(a2),id(a3),id(a4),id(a5))

对于python,他有这么一个机制——intern机制,只让一个“hello”占用内存空间。靠引用计数去维护何时释放。但是应注意,字符串中不能有特殊字符,如(括号、引号、逗号、斜线、反斜线、冒号、句号、问号等等)

小整数池

对于小整数([-5,257)),python已经提前创建好了对象,他们常驻内存,采用引用计数


Python36_垃圾回收_第1张图片
在这里插入图片描述

所以他们有相同的地址,属于同一个对象

对于其他整数


Python36_垃圾回收_第2张图片
在这里插入图片描述

临时创建,属于不同的对象


注意

现在许多IDE都作了一些额外的改进,比如Pycharm,会发现只要是相同常量,他们的地址都相同(似乎与Java类似),但是这并不是python本身的特性


Garbage collection(GC垃圾回收)

现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄漏,悬空指针等bug埋下隐患。对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配垃圾回收内存的问题。python里也同java一样采用了垃圾收集机制,不过不一样的是:python采用的是引用计数机制为主,标记-清除和分代收集机制为辅的策略。

ps:现在常用的python解释器最初是python发明人吉多用c语言写的,称cpython。另还有java语言实现的解释器和其他语言实现的解释器

底层实现

typedef struct_object{
    int ob_refcnt;
    struct_typeobject *ob_type;
}PyObject;

PyObject是每个对象必有的内容,其中ob_refcnt就是作为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少

#define Py_INCREF(op) ((op)->ob_refcnt++)   //增加计数
#define Py_DECREF(op) \ //减少计数
    if (--(op)->ob_refcnt != 0) \
        ; \
    else \
        __Py_Dealloc((PyObject *)(op))

当引用计数为0时,该对象生命就结束了

引用计数机制的优点:

  1. 简单
  2. 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还有一个好处:处理坏收内存的时间分摊到了平时。

引用计数机制的缺点:

  1. 维护引用计数消耗内存资源
  2. 循环引用时,做占的内存永远无法被回收。会导致内存泄漏,注定python还将引入新的回收机制(标记清除和分代收集)。

循环引用示例:

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

对于循环引用的解决:分代回收(Gerneration Zero)

  • 分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率随着对象存活时间的增大而减小。
  • 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉(对于检测出相互引用的,另其计数器同时减1,如果发现他们减1后同时为0,则回收),而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
  • 同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象

gc模块

import gc

常用函数

get_count()

获取当前自动执行垃圾回收器的计数器,返回一个长度为3的列表

set_threshold(threshold0[, threshold1[, threshold2]])

设置自动执行垃圾回收的频率

get_threshold()

threshold 英文意思为临界值

获取gc模块中自动执行垃圾回收的频率,返回一个元组,其3个元素分别表示在什么情况下清理0、1、2代链表,当新创建的对象减去已经释放完的对象的个数大于第一个元素的值,则触发0代的清理;每清理第二个元素次0代链表,就清理一次1代链表,第三个元素的作用类似第二个元素值。

查看与改变gc的状态

  1. 打开gc:gc.enable()
  2. 关闭gc:gc.disable()
  3. 查看gc状态:gc.isenabled()
  4. 显示回收:gc.collect(),即使是disable了,也可以显示的collect
  5. 查看刚刚清理的垃圾列表:gc.garbage,注意,是一个属性,不是方法

其他

导致引用计数+1的情况

  1. 对象被创建,eg:a = 23
  2. 对象被引用,eg:b = a
  3. 对象被作为参数,传入到一个函数中,eg:func(a)
  4. 对象作为一个元素,存储在容器中,eg:my_list = [a, a]

导致引用计数-1的情况

  1. 对象的别名被显示销毁,eg:del a
  2. 对象的别名被赋与新的对象,eg:a = 12
  3. 一个对象离开他的作用域,例如 f 函数执行完毕时,func函数中的局部变量(全局变量不会)
  4. 对象所在的容器被销毁,或从容器中删除对象

查看一个对象的引用计数

import sys
a = "hello world"
sys.getrefcount(a)  #ps,调用此函数查看计数时,会导致其计数+1,因为把a当作参数传入函数了

gc的“bug”

对于一个类,如果重写了__del__方法而没有在重写的方法中调用父类的del方法,就会出现无法清理的情况。即:如果重写了del方法,一定要在重写的方法中调用父类的del方法

你可能感兴趣的:(Python36_垃圾回收)