目录
以引用计数为主,分代回收,标记清除为辅的垃圾回收方式进行内存回收,引入小整数缓冲池和常见简单字符串驻留区的内存缓存池机制。
1、引用计数
1、Python动态类型:引用和对象分离,是动态类型的核心。
2、Python每个对象都维护一个引用技术字段,记录该对象被引用的次数。
3、减少引用计数:
del删除或重新引用时,引用计数会变化(del只是删除引用)
1.1 引用计数器原理
每个对象维护一个 ob_ref 字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1 一旦对象的引用计数为0,该对象可以被回收,对象占用的内存空间将被释放。
它的缺点是需要额外的空间维护引用计数,这个问题是其次的,最主要的问题是它不能解决对象的“循环引用”。
>>> from sys import getrefcount
>>> a = 500
>>> getrefcount(a) 临时引用a,用完即减一
2
>>> b = a
>>> getrefcount(a)
3
>>> lst = [a]
>>> getrefcount(a)
4
1.2 循环引用的情况
x = []
y = []
x.append(y)
y.append(x)
对于上面相互引用的情况,如果不存在其他对象对他们的引用,这两个对象,所占用的内存也还是无法回收,从而导致内存泄漏。
1.3 优缺点
引用计数机制的优点:
- 简单
- 实时性
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用时,无法回收。
2、垃圾回收
2.1 回收原则
当Python的某个对象的引用计数降为0时,可以被垃圾回收。
2.2 gc机制
GC作为现代编程语言的自动内存管理机制,专注于两件事 :
找到内存中无用的垃圾资源,清除这些垃圾并把内存让出来给其他对象使用。 GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在业务逻辑上。
2.3 效率问题
垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。 当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的 次数。当两者的差值高于某个阈值时,垃圾回收才会启动。
import gc
print(gc.get_threshold())
输出:
(700, 10, 10)
2.4 三种情况触发垃圾回收
- 调用gc.collect()
- GC达到阀值时
- 程序退出时
2.5 分代(generation)回收
这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。
Python将所有的对象分为0,1,2三代,所有的新建对象都是0代对象,当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
垃圾回收启动时,一定会扫描所有的0代对象。 如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。 当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
2.6 标记清除
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。
主要用于解决循环引用。
1. 标记:活动(有被引用), 非活动(可被删除)
2. 清除:清除所有非活动的对象
3、内存池机制
3.1 整数对象缓冲池
对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用,避免为整数频繁申请和销毁内存空间。小整数池目的:节省内存,提高执行效率。
而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。终端是每次执行一次,所以每次的大整数都重新创建,而在pycharm中,每次运行是所有代码都加载都内存中。
3.2 字符串驻留区
字符串的intern机制 python对于短小的,只含有字母数字的字符串自动触发缓存机制。其他情况不会缓存。
符合条件则放到驻留区,输入字符串会先去驻留区寻找,可以用is判断。
条件:
多个字符包涵特殊字符,不会放入驻留区,单个字符包涵特殊字符可以放入驻留区。
>>> str1 ="acb"
>>> str2 ="acb"
>>> str2 is str1
True
>>> str2 ="acb "
>>> str1 ="acb "
>>> str2 is str1
False
>>> str2 = "a"*20
>>> str1 = "a"*20
>>> str2 is str1
True
>>> str1 = "a"*21
>>> str2 = "a"*21
>>> str2 is str1
False
>>> str4 = "a#"
>>> str6 = "a#"
>>> str4 is str6
False
>>> str6 = "#"
>>> str4 = "#"
>>> str4 is str6
True
4、深copy,浅copy
d2 = d1.copy() 浅拷贝,拷贝的是地址,只拷贝第一层地址。
(import copy) copy.deepcopy() 深拷贝,拷贝每一层的值,拷贝之后的容器不会影响原来的容器,深拷贝只有这一种情况。
容器内包涵可变数据类型容器的时候,才会有深浅拷贝的区别。
参考文章: