文章目录
1、内存管理机制
以引用计数为主,分代回收、标记清除为辅的垃圾回收方式
以及对小整型进行缓存和简单字符驻留的内存池机制
1.1 引用计数
ptython 中的每个对象都维护一个引用计数 ob_ref字段
- 当有新的引用指向改对象的时候,引用计数+1
- 当无效的引用发生的时候,引用计数-1
- 最后引用技术为0,销毁对象
>>> from sys import getrefcount
>>> a = 1000
>>> getrefcount(a) # getrefcount() 获取引用计数
2 # 当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。
>>> b = a
>>> getrefcount(a)
3
>>> c = []
>>> c.append(b)
>>> getrefcount(a)
4
>>> d = 2000
>>> e = 2000
>>> getrefcount(d)
2
>>> getrefcount(e)
2
>>> d = e = 600
>>> getrefcount(d)
3
>>> getrefcount(e)
3
循环引用
根据引用计数的规律,出现循环引用的情况,内存是无法通过引用计数来释放的,这种情况就会造成内存泄漏。
内存泄漏: 有一部分内存被占用无法释放,进程无法访问
后果: 造成内存溢出(oom—out of memory),内存不够,程序需要的内存大于系统的空闲内存
>>> x = []
>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> y.append(x)
>>> y
[2, [1, [...]]] # 发生死循环
>>> x
[1, [2, [...]]]
>>> getrefcount(x)
3
>>> getrefcount(y)
3
>>> del x # del x;del y引用删除,这块内存区域获取不到了
>>> del y
优缺点
优点:简单、实时性
缺点:维护引用计数消耗资源、循环引用无法回收
1.2 垃圾回收
1) 回收原则
•当Python的某个对象的引用计数降为0时,可以被垃圾回收
2)三种情况触发垃圾回收
• 调用gc.collect()
• GC达到阀值时
• 程序退出时
3)gc机制
• GC作为现代编程语言的自动内存管理机制,专注于两件事
• 找到内存中无用的垃圾资源
• 清除这些垃圾并把内存让出来给其他对象使用。
GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在业务逻辑上。但这并不意味 着码农就可以不去了解GC,毕竟多了解GC知识还是有利于我们写出更健壮的代码
>>> import gc
>>> print (gc.get_threshold())
(700, 10, 10) #
>>> gc.collect() #调用gc.collect()回收
2
>>> gc.collect() #再次调用就没有要回收的了
0
4)回收方式
a.分代(generation)回收
启动垃圾回收的时候确定扫描哪些对象
这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。
• Python将所有的对象分为0,1,2三代。
• 所有的新建对象都是0代对象。
• 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
• 垃圾回收启动时,一定会扫描所有的0代对象。
• 如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。
• 当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
b.标记清除
主要解决循环引用
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。 主要用于解决循环引用。
1. 标记:活动(有被引用), 非活动(可被删除)
2. 清除:清除所有非活动的对象
1.3 缓冲池
1)整数对象缓冲池
对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。
注:对于乘法创建的字符 只会缓冲20个
>>> a = 1 # a和b是一样的
>>> b = 1 # python的整数对象缓冲池
>>> id(a)
140133544871840 # 内存地址一样
>>> id(b)
140133544871840 # 内存地址一样
>>> a = 777 # a和b不是一样的
>>> b = 777
>>> id(a) # 内存地址不同
140133545530064
>>> id(b) # 内存地址不同
140133545530384
>>> a = b = 777
>>> id(a)
140133545530480
>>> id(b)
140133545530480
2)字符串驻留区
为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同。 当触发缓存机制时,只是创造了新的引用,而不是对象本身。
>>> str1 = "abcxyz" 不包含特殊字符的字符串,会放到驻留区
>>> getrefcount(str1)
2
>>> str2 = "abcxyz"
>>> getrefcount(str1)
3
>>> id(str1)
139788884979192
>>> id(str2)
139788884979192
>>> str3 = "#" 单个特殊字符也是会放在驻留区的
>>> str4 = "#"
>>> id(str3)
139788884976056
>>> id(str4)
139788884976056
>>> str5 = "abc 123" 包含了特殊字符(空格),不放在驻留区
>>> str6 = "abc 123"
>>> id(str5)
139788853780696
>>> id(str6)
139788853780808
2、深拷贝与浅拷贝
不属于内存管理
# 只会发生在容器类型里面包含其他可变容器类型的情况
# 浅拷贝可能会造成修改拷贝之后的值
# 浅拷贝只会拷贝第一层的地址,深拷贝则不会
浅拷贝
使用copy()方法,拷贝的是可变对象里的每一个元素的引用地址。整体的引用地址不同
a = {"name": "sc", "score": [80, 90, 100]}
b = a.copy() # 浅拷贝,拷贝a里面每个元素(列表算一个元素)的引用地址
print(id(a), id(b)) # 2346909244224 2346909244416
print(id(a["score"]), id(b["score"])) # 2554998043648 2554998043648
b["score"].append(110)
print(a, b) # 都是{'name': 'sc', 'score': [80, 90, 100, 110]}
print(id(a), id(b)) # 2346909244224 2346909244416
print(id(a["score"]), id(b["score"])) # 2554998043648 2554998043648
使用* 生成的引用,指向的是同一个对象(浅拷贝)
lst = [[]] * 3 # 引用的 [] 是同一个内存地址
print(id(lst), id(lst[0]), id(lst[1]), id(lst[2]))
# 1988547540864 1988544640000 1988544640000 1988544640000
lst[0].append(1)
print(lst)
# [[1], [1], [1]]
print(id(lst), id(lst[0]), id(lst[1]), id(lst[2]))
# 1988547540864 1988544640000 1988544640000 1988544640000
深拷贝
容器里面包含一个可变的容器,才会有深拷贝。
b = copy.deepcopy(a) 只有这种是深拷贝,其他情况都是浅拷贝
import copy
a = {"name": "sc", "score": [80, 90, 100]}
b = copy.deepcopy(a)
print(id(a), id(b)) # 1435380095872 1435380196672
print(id(a["score"]), id(b["score"])) # 1435380502016 1435380501632
b["score"].append(120)
print(a, b) # {'name': 'sc', 'score': [80, 90, 100]} {'name': 'sc', 'score': [80, 90, 100, 120]}
print(id(a), id(b)) # 1435380095872 1435380196672
print(id(a["score"]), id(b["score"])) # 1435380502016 1435380501632
b = []
print(b, id(b)) # [] 2026847155200
for i in range(3):
b.append([]) # 3个不同对象
print(b, id(b)) # [[], [], []] 2026847155200
print(id(b[0]), id(b[1]), id(b[2])) # 2026847155136 2026850055872 2026847157952
b[0].append(1)
print(b, id(b)) # [[1], [], []] 2026847155200
print(id(b[0]), id(b[1]), id(b[2])) # 2026847155136 2026850055872 2026847157952