python - 内存管理
目录
1.0 python引用机制
1.1 python动态类型
对象是储存在内存中的实体。
• 我们在程序中写的对象名,只是指向这一对象的引用(reference)
• 引用和对象分离,是动态类型的核心
• 引用可以随时指向一个新的对象(内存地址会不一样)
#########################################
2.0 python引用计数
在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。
#########################################
2.1 引用计数器原理
每个对象维护一个 ob_ref 字段,用来记录该对象当前被引用的次数
每当新的引用指向该对象时,它的引用计数ob_ref加1
每当该对象的引用失效时计数ob_ref减1
一旦对象的引用计数为0,该对象可以被回收,对象占用的内存空间将被释放。
它的缺点是需要额外的空间维护引用计数,这个问题是其次的
最主要的问题是它不能解决对象的“循环引用
#########################################
2.2 获取引用计数(getrefcount)
#########################################
2.3 增加引用计数
当一个对象A被另一个对象B引用时,A的引用计数将增加1
#########################################
2.4 减少引用计数
del删除或者重新引用时候,引用计数会变化(del只是删除引用)
#########################################
2.5 循环引用的情况
对于上面相互引用的情况,如果不存在其他对象对他们的引用,这两个对象
所占用的内存也还是无法回收,从而导致内存泄漏.
#########################################
2.5.1 内存泄露:
有一部分内存无法被回收释放,进程又无法访问
#########################################
2.5.2 内存溢出:(out of memory)
内存不够用,程序需要的内存大于系统空闲的内存
引用计数可以解决大部分内存释放问题,但是无法解决循环引用问题
#########################################
3.0 python垃圾回收
回收原则:
当某个python对象的引用计数为0时候,可以被垃圾回收
#########################################
3.1 gc机制
GC作为现代编程语言的自动内存管理机制,专注于两件事
• 找到内存中无用的垃圾资源
• 清除这些垃圾并把内存让出来给其他对象使用。
GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放 在业务逻辑上。但这并不意味
着码农就可以不去了解GC,毕竟多了解GC知识还是有利于我们写出更健 壮的代码。
#########################################
3.2 三种情况触发垃圾回收
1.调用gc.collect()
2.gc达到阈值
3.程序退出时候
#########################################
3.3 分代(generation)回收
这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。
• Python将所有的对象分为0,1,2三代。
• 所有的新建对象都是0代对象。
• 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
• 垃圾回收启动时,一定会扫描所有的0代对象。
• 如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。
• 当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描
#########################################
3.4 标记清除
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。
主要用于解决循环引用。
1. 标记:活动(有被引用), 非活动(可被删除)
2. 清除:清除所有非活动的对象
#########################################
4.0 python 缓冲池
4.1 整数对象缓冲池
对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。
a = 200
b = 200
getrefcount(a)
5
getrefcount(b)
5
id(a)
140116833235072
id(b)
140116833235072
>>> a = 1
>>> b = 1
>>> getrefcount(a)
873
>>> getrefcount(b)
873
>>> id(a)
140596260116384
>>> id(b)
140596260116384
>>> a = 257
>>> b = 257
>>> getrefcount(a)
2
>>> getrefcount(b)
2
>>> id(a)
140596125739920
>>> id(b)
140596125740048
#########################################
4.2 字符串缓存
为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同,当触发缓存机制时,只是创造了新的引用,而不是对象本身。
注意:
这对于经常使用的数字和字符串来说也是一种优化的方案
多个字符,含有特殊字符的字符串,不会放在驻留区
str1 = abc
str2 = abc
id(str1)
140116834714936
id(str2)
140116834714936
str1 is str2
True
str1 = abc
str2 = abc
str1 == str2
True
str1 is str2
False
单个字符是特殊字符可以放在驻留区
str1 = #
str2 = #
str1 is str2
True
单个字符重复20次可以在驻留区,重复21次就不在
str1 = a*20
str2 = a*20
str1 is str2
True
str1 = a*21
str2 = a*21
str1 is str2
False
#########################################
4.3 python内存管理总结
引用计数为主,分代回收和标记清除为辅的垃圾回收方式进行内存回收管理
还对小整型缓冲池以及常用字符串驻留区的方式进行内存分配管理
#########################################
5.0 深拷贝与浅拷贝
深浅拷贝主要是针对可变数据类型里面包含可变容器类型的情况
容器里面包容器,才有深浅拷贝的区别
#########################################
1.浅拷贝只会拷贝第一层地址
d1
{'a': [1, 2], 'b': 2}
d2 = d1.copy()
d2
{'a': [1, 2], 'b': 2}
d1 is d2
False
d1['a']
[1, 2]
d1['a'].append(3)
d1
{'a': [1, 2, 3], 'b': 2}
d2
{'a': [1, 2, 3], 'b': 2}
id(di['a'])
Traceback (most recent call last):
File <stdin>, line 1, in <module>
NameError: name 'di' is not defined
id(d1['a'])
140116834121224
id(d2['a'])
140116834121224
#########################################
2.深拷贝会拷贝每一层的值
只有这一种情况才是深拷贝
import copy
copy.deepcopy()
import copy
d5 = d3.copy()
d5
{'a': [1, 2, 3, 4], 'b': 2}
d6 = copy.deepcopy(d5)
d6
{'a': [1, 2, 3, 4], 'b': 2}
d5 is d6
False
d5['a'].append(5)
d5
{'a': [1, 2, 3, 4, 5], 'b': 2}
d6
{'a': [1, 2, 3, 4], 'b': 2}
#########################################
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通