赋值语句内存分析
◆ 使用id()方法访问内存地址
◆ 使用is比较内存引用地址是否相等
在python之中,像数值、字符串、布尔型单纯的赋值,它们只要值相等,那么对应的变量名所在的地址也是相等的,而这就是python对内存管理的优化。相对的,如:[]
列表的复制,其所对应的地址却是不同的。
代码:
def extend_list(val, l=[]): print('---------------------') print(l, id(l)) l.append(val) print(l, id(l)) return l list1 = extend_list(10) list2 = extend_list(123, []) list3 = extend_list('a') print(list1) print(list2) print(list3)
输出如下:
--------------------- [] 1976185016904 [10] 1976185016904 --------------------- [] 1976215339528 [123] 1976215339528 --------------------- [10] 1976185016904 [10, 'a'] 1976185016904 [10, 'a'] [123] [10, 'a']
结论:对比list1、list2、list3的输出结果后,就能了解在函数中默认的列表,其在python的内存存储机制。list2不同的原因,就在于它使用的是新生成的列表,而list1和list3相同的原因,则是它们使用的是函数中默认的列表,在内存中,它们是同一个。
垃圾回收机制
◆ 以引用计数为主,分代收集为辅
◆ 如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存
◆ 引用计数的缺陷是循环引用的问题
代码(1):
class Cat(object): def __init__(self): print('对象产生了:{0}'.format(id(self))) def __del__(self): print('对象删除了:{0}'.format(id(self))) def f0(): while True: c1 = Cat() if __name__ == '__main__': f0()
输出如下:
对象产生了:1775928937992 对象删除了:1775928938312 对象产生了:1775928938312 对象删除了:1775928937992 对象产生了:1775928937992 对象删除了:1775928938312 对象产生了:1775928938312 对象删除了:1775928937992
代码(2):
class Cat(object): def __init__(self): print('对象产生了:{0}'.format(id(self))) def __del__(self): print('对象删除了:{0}'.format(id(self))) def f1(): l = [] while True: c1 = Cat() l.append(c1) print('列表元素:', len(l), '个') if __name__ == '__main__': f1()
输出如下:
对象产生了:1627687778248 列表元素: 88839 个 对象产生了:1627687778312 列表元素: 88840 个 对象产生了:1627687778376 列表元素: 88841 个 对象产生了:1627687778440 列表元素: 88842 个 对象产生了:1627687778504 列表元素: 88843 个
结论:对上面两个代码和输出结果对比,能发现。第一个代码因为后续没有再对之前的生成进行使用,所以虽然不是立刻删除,但也是会马上的进行删除,这样,内存的使用空间不会一直的增长。相对的,第二个代码,它把生成的对象存放到了列表之中,这个列表对之前生成的所有对象进行了引用,所以其存在就不会被删除,内存空间早晚会溢出。
引用计数(reference count)
◆ 每个对象都存有指向该对象的引用总数
◆ 查看某个对象的引用计数
sys.getrefcount()
◆ 可以使用del关键词删除某个引用
代码:
import sys i = 1 s = [] l2 = s l3 = s l5 = l3 print(sys.getrefcount(s)) del l2 #对象l被引用的数量 print(sys.getrefcount(s)) print('------------------') print(sys.getrefcount(i)) c = i print(sys.getrefcount(i))
输出如下:
5 4 ------------------ 96 97
结论:可以很明显的看出,引用数不一定是从1开始,这涉及到python当中的内存共享的机制。而每一次的直接引用或者间接引用都会使引用数增加,而对s的引用次数的观察,可发现多了1,这是因为当我们输出的时候,也会对其进行引用,所以引用次数会多一次。
垃圾回收
◆ 满足特定条件,自动启动垃圾回收
◆ 当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数
◆ 当两者的差值高于某个阈(yù)值时,垃圾回收才会启动
◆ 查看阈值gc.get_threshold()
gc — Garbage Collector interface
代码:
import gc print(gc.get_threshold())
输出如下:
(700, 10, 10)
分代回收
◆ Python将所有的对象分为0,1,2三代
◆ 所有的新建对象都是0代对象
◆ 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象
PS:即初始为0代,每次回收不掉,就对其进行代数的提升,最高为2代。而其有阈值存在,当0代回收次数到达一定的阈值,才会回收1代,而1代回收到一定的阈值,才会回收2代。而1代和2代的回收,会把前面的回收再执行。
手动回收
◆ gc.collect()手动回收
◆ objgraph模块中的count()记录当前类产生的实例对象的个数
代码(1):
import gc, sys import objgraph print(gc.get_threshold()) class Persion(object): pass class Cat(object): pass p = Persion() c = Cat() p.name = 'Susan' p.pet = c c.master = p print(sys.getrefcount(p)) print(sys.getrefcount(c)) del p del c # 手动执行垃圾回收 gc.collect() print(objgraph.count('Persion')) print(objgraph.count('Cat'))
输出如下:
(700, 10, 10) 3 3 0 0
代码(2):
# 上面代码注释掉这两行代码 # del p # del c
输出如下:
(700, 10, 10) 3 3 1 1
结论:如果不进行手动回收,那么就会导致回收不掉的。
内存管理机制
◆ 内存池(memory pool)机制
当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。内存池的概念就是预先再内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。
◆ Python3中的内存管理机制——Pymalloc
针对小对象(<=512bytes),pymalloc会在内存池中申请内存空间
当>512bytes,则会PyMem_RawMalloc()和PyMem_RawRealloc()来申请新的内存空间
◆ 单位换算
1 Byte = 8 Bit (即 1B = 8b)
1 KB = 1024 Bytes
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
1 EB = 1024 PB
1 ZB = 1024 EB
1 YB = 1024 ZB
PS:Bit意为"位"或"比特",时计算机运算的基础,属于二进制的范畴;Byte意为"字节",时计算机文件大小的基础计算单位。
结论
慕课网 时间_记录昨天 :
从三方面来说:引用计数、垃圾回收机制、内存池机制。可以根据一下描述进行理解
一、对象的引用计数机制
Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。
引用计数增加的情况:
1、一个对象分配一个新名称
2、将其放入一个容器中(如列表、元组或字典)
引用计数减少的情况:
1、使用del语句对对象别名显示的销毁
2、引用超出作用域或被重新赋值
sys.getrefcount( )函数可以获得对象的当前引用计数
多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。
二、垃圾回收
1、当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
2、当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。
三、内存池机制
1、Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
2、Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
3、Python中所有小于256个字节的对象都是用pymalloc实现的分配器,而大的对象则使用系统的malloc,另外Python对象比如整数浮点数和list都有独立的私有内存池,对象间不共享他们的内存池,也就是说你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。