注意,垃圾回收机制的测试建议以交互模式测试
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已经提前创建好了对象,他们常驻内存,采用引用计数

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

临时创建,属于不同的对象
注意
现在许多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时,该对象生命就结束了
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还有一个好处:处理坏收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗内存资源
- 循环引用时,做占的内存永远无法被回收。会导致内存泄漏,注定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的状态
- 打开gc:gc.enable()
- 关闭gc:gc.disable()
- 查看gc状态:gc.isenabled()
- 显示回收:gc.collect(),即使是disable了,也可以显示的collect
- 查看刚刚清理的垃圾列表:gc.garbage,注意,是一个属性,不是方法
其他
导致引用计数+1的情况
- 对象被创建,eg:a = 23
- 对象被引用,eg:b = a
- 对象被作为参数,传入到一个函数中,eg:func(a)
- 对象作为一个元素,存储在容器中,eg:my_list = [a, a]
导致引用计数-1的情况
- 对象的别名被显示销毁,eg:del a
- 对象的别名被赋与新的对象,eg:a = 12
- 一个对象离开他的作用域,例如 f 函数执行完毕时,func函数中的局部变量(全局变量不会)
- 对象所在的容器被销毁,或从容器中删除对象
查看一个对象的引用计数
import sys
a = "hello world"
sys.getrefcount(a) #ps,调用此函数查看计数时,会导致其计数+1,因为把a当作参数传入函数了
gc的“bug”
对于一个类,如果重写了__del__
方法而没有在重写的方法中调用父类的del方法,就会出现无法清理的情况。即:如果重写了del方法,一定要在重写的方法中调用父类的del方法
网友评论