1、内存管理机制 以引用计数为主,分代回收、标记清除为辅的垃圾回收方式
以及对小整型进行缓存和简单字符驻留的内存池机制
2、引用计数 ptython 中的每个对象都维护一个引用计数 ob_refz字段
当有新的引用指向改对象的时候,引用计数+1
当无效的引用发生的时候,引用计数-1
最后引用技术为0,销毁对象
>>> from sys import getrefcount
>>> a = 1000
>>> getrefcount(a)#实际是引用了1次
2
>>> b = a
>>> getrefcount(a)
3
>>> c = []
>>> c.append(b)
>>> getrefcount(a)
4
>>> getrefcount(a)
3
>>> d = 2000
>>> e = 2000
>>> getrefcount(d)
2
>>> getrefcount(e)
2
>>> d = e = 600
>>> getrefcount(d)
3
>>> getrefcount(e)
3
根据引用计数的规律,出现循环引用的情况,内存是取法通过引用计数来释放
这种情况就会造成内存泄漏
内存泄漏: 有一部分内存被占用无法释放,进程无法访问
后果:造成内存溢出(oom—out of memory),内存不够,程序需要的内存大于系统的空闲内存
>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> y.append(x)
>>> getrefcount(x)
3
>>> getrefcount(y)
3
>>> del x
>>> del y
3、垃圾回收 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.标记清除 主要解决循环引用
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。 主要用于解决循环引用。
- 标记:活动(有被引用), 非活动(可被删除)
- 清除:清除所有非活动的对象
对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。
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
5、深拷贝与浅拷贝 不属于内存管理
只会发生在容器类型里面包含其他可变容器类型的情况
浅拷贝可能会造成修改拷贝之后的值
浅拷贝只会拷贝第一层的地址,深拷贝则不会
>>> a = {"name":"sc","score":[80,90,100]}
>>> b = a.copy()浅拷贝,拷贝a里面每个元素(列表算一个元素)的引用地址
>>> b {'name': 'sc', 'score': [80, 90, 100]}
>>> b["score"].append(110)
>>> b {'name': 'sc', 'score': [80, 90, 100, 110]}
>>> a {'name': 'sc', 'score': [80, 90, 100, 110]}
>>>> id(a)
139788853767048
>>> id(b)
139788853766328
>>> id(a["score"])
139788884386760
>>> id(b["score"])
139788884386760
【python笔记|Python 内存管理】容器里面包含一个可变的容器,才会有深拷贝
b = copy.deepcopy(a) 只有这种是深拷贝,其他情况都是浅拷贝
>>> import copy
>>> b = copy.deepcopy(a)只有这种是深拷贝,其他情况都是浅拷贝
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> id(a)
139788853767048
>>> id(b)
139788853766760
>>> id(a["score"])
139788884386760
>>> id(b["score"])
139788846326728
>>> b["score"].append(120)
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110, 120]}
>>> lst = [[]]*3拷贝了同一个地址
>>> lst
[[], [], []]
>>> lst[0].append(1)
>>> lst
[[1], [1], [1]]
>>> print(id(lst[0]),id(lst[1]),id(lst[2]))
139788846326792 139788846326792 139788846326792
推荐阅读
- python笔记|Python迭代器与生成器
- python笔记|Python 面向对象(一)
- #|数据分析与可视化(四)Pandas学习基础一(统计分析基础)
- 数据分析|机器学习多元线性回归模型(公式推导以及代码实现)
- 机器学习|机器学习(2)-朴素贝叶斯的理解和代码实现
- numpy|numpy库的使用-读取数据
- python|机器学习--朴素贝叶斯分类器(python手动实现)
- python|python-pandas dataframe正则筛选包含某字符串的列数据str.contains()
- Python全栈系列[更新中]|Python零基础入门篇 - 53 - 文件的读取