python的垃圾回收

引用计数器为主,标记清除和分代回收为辅

1 引用计数器

在这里插入图片描述

在python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段来进行创建相关的数据,然后将对象添加到refchain双像链表中,每个对象中的ob_refcnt就是引用计算器,值默认是为1,当有其他的变量引用对象时,引用计数器就会发生变化。

当一个对象的ob_refcnt为0时,会进行垃圾回收,将对象从refchain链表中移除,将对象销毁,内存回收

2 标记清除

标记清除存在的问题:循环引用
在这里插入图片描述
在这里插入图片描述
为了解决循环引用的问题,python引入了标记清除的技术:在python底层再维护一个链表,链表中专门存放可能存在循环引用的的对象(list/tuple/dict/set),在python内部的某种情况下下触发会去扫描标记情况中的每个元素,检查是否有循环引用如果有则让双方的引用计数-1,refcnt为0则垃圾回收。

3 分代回收

标记清除存在两个问题

  1. 什么时候去扫描标记清除的链表
  2. 扫描标记清除的链接扫描的代价大,每次扫描耗时比较久

将标记清除的链表分为三代

  • 0代:0代中对象个数达到700个扫描一次
  • 1代:0代扫描达到10次,则1代扫描一次
  • 2代:1代扫描10次,则2代扫描一次

分代清除解决什么时候去扫描扫描耗时的问题,当0代的对象个数达到700时会扫描0代计数为0的垃圾回收,否则升级为1代,当0代扫描了10次时会扫描1代,计数器为0清除,否则升级为2代

4 python缓存机制

为了优化python对内存的使用底层使用了多种缓存机制

在这里插入图片描述

4.1 小整数池

a = 10
b = 10

print(a == b)   # True
print(a is b)   # True

python 中经常使用的一些数组定位为小整数池,小整数池的范围为**-5~256**,python对这些数值已经提前创建好了内存地址,即使多次重新定义也不会重新开辟新的空间,但小整数池外在重新定义时会再次开辟新的空间,锁小整数池中的内存地址时相同的

4.2 不可变类型缓存

a = 100000000000000000
b = 100000000000000000

print(a == b)   # True
print(a is b)   # True

a = "aaa"
b = "aaa"
print(a == b)   # True
print(a is b)   # True

a = 12.2
b = 12.2
print(a == b)   # True
print(a is b)   # True

a = (1, 2, 3)
b = (1, 2, 3)
print(a == b)   # True
print(a is b)   # True

pyhon解释器启动时会先从内存中开辟出来一小块内存,用于存储高频使用的数据(不可变类型,数字、字符串、元组),这样可以大大减小使用数据的对象时申请内存和销毁内存的开销。在同一块代码下,不可变类型的对象被多个变量引用,不会重复开辟内存空间,可变类型(字典、列表、集合)被多个变量创建时会重新开辟新的内存地址

而交互模式下,不会使用缓存机制

4.3 字符驻留

#交互模式
>>> s1='hello'
>>> s2='hello'
>>> print(s1 is s2)
True

字符串作为python最为常用的数据类型,python为了提高字符串的使用效率和使用性能,使用了intern(字符串驻留)来提高字符串的效率,即使同样的字符串对象仅仅会保存一份,放在一个和字符串储存池中,是共用的,有新的变量引用同样的字符串时候,不会开辟新的内存空间,而是引用这个共有的字符串。所以在交互模式下字符也是同一个对象

满足字符驻留的条件

  1. 在交互模式下,只包含字母数字下划线的字符串才会触发 intern 机制
  2. 在 IDE 环境或者脚本模式下,只要长度不超过20(长度限制),即使使用特殊字符也会触发 intern 机制
  3. 用的是 python 3.9,发现没有长度限制了,都会触发 intern 机制

4.4 free_list

当一个对象的引用计算器为0时,按道理说应该回收,但是内部不会直接直接将开辟的内存空间直接回收,而是将对象放在free_list链表中当缓存,以后再去创建对象时不再重新开辟内存而是直接使用free_list的对象。(float/list/tuple/dict)

v1 = 3.14
del v1

当运行这段代码会创建一个float对象,并将其加到refchain中。接下来执行删除操作,这时候v1的引用计数器变为零,这时候理应对v1执行垃圾回收,将其从refchain中进行摘除,并从内存中进行消除。但是实际上,Python会将这个对象存放到free_list中。当以后创建一个变量v9=999.99,这时候Python就不会重新开辟内存,而是直接从free_list获取这个对象,获取到对象之后对对象中的数据进行初始化,再放到refchain中,这也是一种优化机制。

free_list不会把每一个对象都放进来,free_list假设最多能放80个,当del了80个,这些对象都会存放在free_list中,当del第81个对象时,因为free_list已经满了,因此不会缓存到free_list中,这个时候才会对对象进行销毁。

5 源码分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.1 创建float对象

var = 3.14

在这里插入图片描述
创建一个float对象的时候

  1. 首先会去查询free_list中是否为空,为空会malloc一块新的内存空间,否则从free_list中获取一块内存。
  2. 之后对PyObject初始化如引用计数器初始化为1,添加到refchain链表中
  3. PyObject对象的ob_fval进行赋值
  4. 返回PyObject的指针

5.2 引用 float对象

val = 3.14
val1 = val

在这里插入图片描述
出现引用的时候,会将原来PyObject中的ob_refcnt+1

5.3 销毁float对象

var = 3.14
del var

在这里插入图片描述
销毁对象时,会将引用计数器-1,如果引用计数器为0则执行Dealloc进行垃圾回收,否则啥事情都不做

相关推荐

  1. python基础---垃圾回收

    2024-04-03 22:20:04       24 阅读
  2. (五)Python 垃圾回收机制

    2024-04-03 22:20:04       50 阅读
  3. Python内存管理与垃圾回收机制

    2024-04-03 22:20:04       62 阅读
  4. 谈谈Python内存管理和垃圾回收机制

    2024-04-03 22:20:04       36 阅读
  5. Python垃圾回收机制是什么】

    2024-04-03 22:20:04       24 阅读
  6. GC垃圾回收定义

    2024-04-03 22:20:04       55 阅读
  7. GC垃圾回收算法

    2024-04-03 22:20:04       40 阅读
  8. jvm垃圾回收策略

    2024-04-03 22:20:04       37 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-03 22:20:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-03 22:20:04       101 阅读
  3. 在Django里面运行非项目文件

    2024-04-03 22:20:04       82 阅读
  4. Python语言-面向对象

    2024-04-03 22:20:04       91 阅读

热门阅读

  1. C++引用python代码

    2024-04-03 22:20:04       44 阅读
  2. 信奥赛一本通 【例4.2】天安门广场的面积

    2024-04-03 22:20:04       38 阅读
  3. pygame--坦克大战(二)

    2024-04-03 22:20:04       38 阅读
  4. 供应商管理软件:供应商绩效评估实用清单

    2024-04-03 22:20:04       40 阅读
  5. ChatGPT学术写作攻略:让论文更具深度

    2024-04-03 22:20:04       38 阅读
  6. 宝塔面板无法访问 404 not found

    2024-04-03 22:20:04       32 阅读
  7. Memcached 教程之 Memcached add 命令(六)

    2024-04-03 22:20:04       34 阅读