python笔记|Python 内存管理

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.标记清除 主要解决循环引用
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。 主要用于解决循环引用。
  1. 标记:活动(有被引用), 非活动(可被删除)
  2. 清除:清除所有非活动的对象
4、缓冲池 1)整数对象缓冲池
对于[-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

    推荐阅读