01-小整数对象池、intern机制
列表在使用的过程中,即使数据相同,但他们依然是各自的一份,不会复用,用id(var)
可以查看这个变量的内存地址。
相同内容的列表,他们的地址不同;对于相同小整数内容的对象池,他们的地址相同
python解释器为了能让程序运行的更快,早就把常用的数据,比如1、2、3等,在python解释器运行的开始阶段就创建了,就只等待被使用了。因为开发人员用这个用的多。
除了1、2、3之外,还有很多,这个范围是[-5, 256]
每一个大整数,均创建一个新的对象。([-5, 256]就是小整数对象池)
python中的另一个特殊机制:字符串驻留:因为字符串不可被修改的,默认自动开启对普通字符串开启intern机制,也就是共用同一份,但如果字符串中包含空格等特殊字符,就不会开启。【这些机制在不同系统中有时会不太一样,比如windows和ubuntu中】
当定义两个相同的字符串时,引用计数为0,会开启垃圾回收(python机制:能少用内存就少用内存,所有语言都是这样其实)
02-垃圾回收:引用计数、循环计数
python采用的是引用计数为主,分代收集机制为辅的策略(了解即可)
C语言、C++都是自己要管理维护内存,极其灵活,有好处也带来坏处,经常内存泄漏。例如malloc(100)申请100个字节,使用free()进行释放,如果忘记释放,遗留的内存就是垃圾,这种遗留内存的现象就是内存泄漏。
虽然python更智能,但是有时也会留下一部分清理不了的内存,这就是我们研究gc的原因。
python为了能够知道当前这个对象有多少个变量指向它,会在每个对象中有一个小小的空间,里面存放 引用计数。
比如a = XXX() b=a
这时候那个引用计数变为2
引用计数有优点,也有缺点:
优点:简单,实时性(一旦没有引用,内存就直接被释放了,不用像其他机制等待时机,这样就把垃圾处理的时间均摊了)
缺点:维护引用计数消耗资源;解决不了循环引用的问题
循环引用非常危险(附图)
03-Ruby与Python对比,理解垃圾回收
英文原文(这个文章解释垃圾回收非常好):visualizing garbage collection in ruby and python
ruby在代码执行前,创建成千上百个对象,串在链表上,名曰:可用列表。什么时候用空间,什么时候从上面取。
python是什么时候创建对象,什么时候申请内存,什么时候饿了什么时候做一个馒头
当ruby在指向前面链表元素的变量指向后面的时候,前面的不会自动回收【ruby太懒,攒到没有盘子用的时候才去刷】。python这样的时候,那个位置的引用计数就变成0了,python就给删掉了【python太勤快,一个盘子不用了就去刷一个盘子】
Ruby这种方法叫做“标记-清除”,用0-1来标记这个位置,被称为可用位图,来跟踪对象是否被标记了。如果标记的对象是存活的,那么剩下的未标记的对象就只能是垃圾,通过调整内部指针,将它指向一个新链表的方式,来将垃圾回收对象归位到可用列表中。
python中是引用计数:在每个对象中预留空间来处理引用计数,实现比较难;处理相对较慢,不停的更新着众多的引用数值;
python中怎么避免循环引用:引用计数能够解决大部分问题,但解决不了循环引用,所以采用隔代收集的方法:python会创建一个零代列表,找出列表中每个互相引用的对象。根据规则减掉它的引用计数。
也就是如果没有变量指向这个对象,但仍有引用计数,那么意味着它被循环引用了。把经过减引用计数后还不是0的元素移动到新的链子上,然后把留下来的这些称作第一代(从第0代移动过来的),最多有3代。
(从某种意义上说,这种方法类似ruby的标记回收算法)
什么时候才会触发这个减引用计数呢?有个条件,使用gc的阈值。毕竟不能时时刻刻都在做这件事。
python解释器一直在跟踪着这个阈值。
ubuntu打开top
,看程序的占用cpu和内存
如果使用导入gc,然后调用gc.disable()
,就关掉了python的内存自动回收,如果有内存泄漏,并且while True
,那肯定就崩掉了
美国有一架航天飞机就是因为内存泄漏失事了,特斯拉汽车如果内存泄漏,也会出事
04-解决循环引用的方式:隔代收集
三种情况下会触发垃圾回收:
1、gc模块计数器(申请对象个数-释放对象个数)达到阈值后,自动回收垃圾
2、调用gc.collect()
,手动回收垃圾【在没有达到条件时,强制清】
3、程序退出的时候,python解释器来回收垃圾
导致引用计数+1的情况:
1、对象被创建:a=23
2、对象被引用:b=a
3、对象作为参数传入函数:func(a)
4、对象作为一个元素存在容器中,例如list1=[a,a]
(这里+2)
导致引用计数-1的情况:
1、对象别名被显式销毁,del a
2、对象的别名被赋予新的对象,例如a=24
3、对象离开他的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
4、对象所在的容器被销毁,或者从容器中删除对象
import sys.getrefcount(a)
获取引用计数的数量
pycharm对默认的python创建的对象自己有一定的优化,所以在pycharm里面测试的话会和理论上不一样
隔代收集:
把对象分成三条链子,这个链子永远存在,分别叫第0代、第1代、第二代。gc模块中有一个长度为3的列表的计数器,可以通过gc.get_count()
获取
第1代根据第0代触发了多少次才触发
第2代根据第1代触发了多少次才触发
可以把第0代想象成秒,第1代想象成分,第2代想象成时
gc模块有一个阈值,可以根据gc.get_threshold()
,默认为(700,10,10),也就是:
清理第0代的链子的条件是,垃圾数据达到700个;
清理第1代的链子的条件是,第0代链子清理了10次;
清理第2代的链子的条件是,第1代链子清理了10次;
那么在第三条链子上存在的对象,这样的对象说明是程序中的核心,因为经过无数次的清洗,依然在
怎么获取当前一共多少个垃圾,一共清理了多少次
gc.get_count()
—> (223, 7, 4) 说明第0条链子上有223个垃圾,第0条清理过7次,第1条清理过4次
(思考:在运行程序时,可以先用gc.disable()
增加计算速度,然后到最后再开启垃圾回收gc.enable()后者gc.collect()
调用一次)