Python进阶之垃圾回收机制

**

一、引用计数器

在Python中创建name为Nick,age为20这两个变量,会被记录到refchain 这个链表中。

在refchain每个对象中都有一个ob_refcnt(引用计数器)。

通过getrefcount方法可以查看对象的引用计数的个数,由于作为getrefcount这个函数的参数,所以计数会+1.

python 复制代码
import sys
​
name = "Nick"
age = 22
​
print(sys.getrefcount(name))# 2
print(sys.getrefcount(age)) # 2
import sys
​
name = "Nick"
age = 22
​
# age的引用计数器加一
count = age
​
print(sys.getrefcount(name))# 2
print(sys.getrefcount(age)) # 3
复制代码

当值被多次引用的时候,不会被重复创建,而是引用计数器+1,当对象被销毁时,引用计数器-1。一般来说,当引用计数器为0时,将对象从refchain移除,同时在内存中进行销毁。

二、标记清除

python 复制代码
# 循环引用
list_1 = [1,2,3]
list_2 = [1,2,3]
​
​
print(sys.getrefcount(list_1)) # 2
print(sys.getrefcount(list_2)) # 2
​
​
# 列表1中添加list2
list_1.append(list_2)
# 列表2中添加list1
list_2.append(list_1)
​
print(sys.getrefcount(list_1)) # 3
print(sys.getrefcount(list_2)) # 3

如果对象进行循环引用,仅通过引用计数器来回收,会造成部分引用计数器不为0,则一直占用内存。 Python中便引入了标记清除的技术,针对可能存在的循环引用的对象进行特殊的处理(列表、元祖、字典、集合等可以嵌套的对象)。

标记清除:创建特殊链表专门保存列表、元祖、字典、集合等对象,之后去检查这个链表中的对象是否存在循环引用,如果存在则双方的引用器互相-1.

三、分代回收

有了标记清除的技术可以解决引用计数器的问题,那么什么时候进行清除又是一个问题,Python底层提供了分代回收的技术解决什么时候进行清除,到多少才清除。

分代回收:对标记清除中的链表进行优化,将那些可能存在循环引用的对象拆分为3个链表,0、1、2三代,每代都可以存储对象和阈值,就会对相应的链表中的每个对象做一次扫描,除循环引用各自-1并且销毁引用计数器为0的对象。

cpp 复制代码
//Include/internal/mem.h
​
struct gc_generation {
PyGC_Head head; 
int threshold; /* collection threshold  阈值*/ 
int count; /* count of allocations or collections of younger
generations  该链表中对象的个数*/
};
#define _GEN_HEAD(n) GEN_HEAD(gcstate, n)
    struct gc_generation generations[NUM_GENERATIONS] = {
        /* PyGC_Head,                                    threshold,    count */
        {{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)},   700,        0},
        {{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)},   10,         0},
        {{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)},   10,         0},
    };
    for (int i = 0; i < NUM_GENERATIONS; i++) {
        gcstate->generations[i] = generations[i];
    };
    gcstate->generation0 = GEN_HEAD(gcstate, 0);
    struct gc_generation permanent_generation = {
          {(uintptr_t)&gcstate->permanent_generation.head,
           (uintptr_t)&gcstate->permanent_generation.head}, 0, 0
    };
    gcstate->permanent_generation = permanent_generation;
}

|---------------------------|--------------------------|--------------------------|
| 0代 | 1代 | 2代 |
| 阈值700 | 阈值10次 | 阈值10次 |
| 当0代链表超过了700个对象 就会触发垃圾回收机制 | 当0代触发了10次垃圾回收机制 就会触发1代回收 | 当1代触发了10垃圾回收机制 就会触发2代的回收 |

四、缓存机制

反复的创建和销毁会使程序执行效率低下,Python引入了缓存机制。

1、float 类型,维护的free_list链表可缓存100个float对象

cpp 复制代码
demo_f1 = 4.44
print(id(demo_f1))# 2346495219728
# 删除demo_f1
del demo_f1
demo_f2 = 999.9 
print(id((demo_f2))) #2346495219728
复制代码

2、int类型,不是基于free_list,而是维护一个small_list链表保存常见的数据(小数池),-5<=value<=257

cpp 复制代码
demo_int = 200
print(id(demo_int)) #140722188028384
del demo_int
demo_int2 = 200
print(id(demo_int2)) #140722188028384

3、str类型,维护unicode_latin1[256]链表,内部将所有的ASCill缓存起来。

cpp 复制代码
demo_str = "A"
print(id(demo_str))#2281351493296
​
del demo_str
demo_str1 = "A"
print(id(demo_str1))#2281351493296
复制代码

4、List类型,维护free_list数组最多可以缓存80list对象

cpp 复制代码
demo_list = [1,2,3]
print(id(demo_list))#1522843472456

del demo_list
demo_list1 = [4,5,6]
print(id(demo_list1))#1522843472456

5、tuple类型,维护一个free_list数组且数组的容量为20,数组中的元素可以是链表且每个链表最多可以容纳2000个元祖对象。

cpp 复制代码
s = (1,2,3)
print(id(s)) # 2575098184312

del s

s1 = (4,5,6)
print(id(s1)) # 2575098184312

6、dict类型,维护的free_list数组最多可以缓存80个dict对象。

cpp 复制代码
demo_dict = {"name":'lw'}
print(id(demo_dict))# 2541345852296
del demo_dict
demo_dict1 = {"name":"lw"}
print(id(demo_dict1))# 2541345852296
数据类型 缓存容量
int类型 small_list链表保存常见的数据(小数池),-5<=value<=257。
str类型 维护unicode_latin1[256]链表,内部将所有的的ASCII缓存起来
float类型 free_list链表可缓存100个float对象
List类型 维护free_list数组最多可以缓存80个list对象
tuple类型 维护一个free_list数组且数组的容量为20,数组中的元素可以是链表且每个链表最多可以容纳2000个元祖对象。
dict类型 维护的free_list数组最多可以缓存80个dict对象

五、小结

问:你知道Python的垃圾回收机制吗? 谈谈你的理解。

Python中垃圾回收机制,以引用计数器为主,标记清除和分代回收为辅。 一般来说,通过引用计数器即可解决变量使用,但如果是互相引用的数据类型就需要标记清除的技术,分代回收是解决什么时候清除,清除多少。

参考\] [pythonav资源分享![icon-default.png?t=N7T8](https://file.jishuzhan.net/article/1783370253288345602/c9868f5524aa31bbc2ae673827f18730.webp)https://pythonav.com/wiki/detail/6/88/#1.%20%E7%99%BD%E8%AF%9D%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6](https://pythonav.com/wiki/detail/6/88/#1.%20%E7%99%BD%E8%AF%9D%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6 "pythonav资源分享") [mirrors / python / cpython![icon-default.png?t=N7T8](https://file.jishuzhan.net/article/1783370253288345602/c9868f5524aa31bbc2ae673827f18730.webp)https://gitcode.net/mirrors/python/cpython](https://gitcode.net/mirrors/python/cpython "mirrors / python / cpython")

相关推荐
大佬,救命!!!2 分钟前
etp中未运行用例顺序的定位及补齐脚本自动化生成
python·学习笔记·excel·自动化脚本·用例整理清洗
KevinCyao5 分钟前
php彩信接口代码示例:PHP使用cURL调用彩信网关发送图文消息
android·开发语言·php
装疯迷窍_A9 分钟前
以举证方位线生成工具为例,分享如何在Arcgis中创建Python工具箱(含源码)
开发语言·python·arcgis·变更调查·举证照片
2402_8548083711 分钟前
CSS如何实现元素在容器内居中_利用margin-auto技巧
jvm·数据库·python
weixin_5806140012 分钟前
html标签怎么表示用户输入_kbd标签键盘快捷键标注【介绍】
jvm·数据库·python
m0_7164300713 分钟前
如何监控集群 interconnect_ping与traceroute验证心跳通畅.txt
jvm·数据库·python
m0_6784854514 分钟前
如何通过 curl 调用 Go 标准库 RPC 服务(JSON-RPC 协议)
jvm·数据库·python
网域小星球14 分钟前
C 语言从 0 入门(二十五)|位运算与位段:底层开发、嵌入式核心
c语言·开发语言
Gofarlic_OMS24 分钟前
ENOVIA基于Token的许可证消费模式分析与分点策略
java·大数据·开发语言·人工智能·制造
2401_8654396326 分钟前
HTML5中SVG原生动画标签Animate的基础用法
jvm·数据库·python