Python的垃圾回收与内存泄露

Python的垃圾回收与内存泄露,第1张

Python的垃圾回收与内存泄露

文章目录
  • 基础知识
    • 容器
    • 栈区和堆区
    • 直接引用和间接引用
    • 循环引用
  • 垃圾回收
    • 定义
    • 方法
      • 引用计数
      • 标记清除
      • 分代回收
    • 总结
  • 内存泄露
    • 定义
    • 原因
    • 解决方法
    • 定位内存泄露的位置
  • 参考

基础知识 容器

哪些是容器对象:列表、数组、字典、元组、类和实例等

栈区和堆区

1、我们在定义变量的时候,变量名和变量值都是需要存储的,分别对应内存中的两块区域:栈区和堆区。
2、栈区:存放变量名与值内存地址的关联关系
3、堆区: 存放变量值。内存管理回收的就是堆区的内容。
例如:定义了两个变量x=10, y=20,具体解释如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BueiUsla-1640616720544)(Python的垃圾回收与内存泄露_files/74577840.png)]
当我们执行x=y时,内存中的栈区与堆区变化如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbgEsesK-1640616720546)(Python的垃圾回收与内存泄露_files/74637541.png)]

直接引用和间接引用

1、直接引用:从栈区出发直接引用到内存地址。
2、间接引用:从栈区出发引用到堆区后,再通过进一步引用才能到达内存地址。
间接引用值存在与容器中,例如:列表、字典、元组等。容器的变量名存储着整个容器的内存地址,容器中又存储的是每个元素的内存地址。
3、直接引用的例子:

x = 10
y = “刘翔”
z = [1, "good"] 

4、间接引用的例子:

list_1 = [1, 2]  # 列表本身被变量名list_1直接引用,包含的元素被列表间接引用
x = 10  # 值10被变量名x直接引用
list_2 = [x, list1]  # 列表本身被list_2直接引用,包含的元素被列表间接引用

5、解释图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLtrnPIw-1640616720547)(Python的垃圾回收与内存泄露_files/75386194.png)]
6、容易犯错的地方:

x  = 10
y = ["good",  x]
x = 20
print(x)
print(y)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RQwTBcR-1640616720548)(Python的垃圾回收与内存泄露_files/75655565.png)]

循环引用

1、定义:两个对象之间分别相互引用对方。
2、引发的问题:当出现对象(变量值)不再被任何变量名所引用(那就得回收)时,由于它的引用计数不会为0,所以会导致该对象永远(变量值/内存)不会被回收。这样最终的结果会导致内存泄露!
2、例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iq2J1E0O-1640616720548)(Python的垃圾回收与内存泄露_files/77139319.png)]
3、解释图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HkDYDWVd-1640616720550)(Python的垃圾回收与内存泄露_files/77175896.png)]
4、当执行”del l1"和“del l2”之后,列表l1和l2的引用计数减去1,变成了1。此时只剩下列表l1和l2的的互相引用了。
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOCnLMG9-1640616720551)(Python的垃圾回收与内存泄露_files/77617231.png)]
5、解决循环引用的办法:后面会提到的“标记清除法”

垃圾回收 定义

python一切皆为对象,是对象就必须申请内存进行存储。有申请就必须要有回收,否则再大的内存也不够用。所以垃圾回收的对象就是:python解释器内部那些没有被任何地方引用到的对象。

方法

简单一句话就是:引用计数为主,标记清除和分代回收为辅。更具体来说是:Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。

引用计数

1、引用计数/what:内存中的值被引用的次数。当计数为0时,该变量值就成为了垃圾,就会被回收。
2、引用计数加1的 *** 作有/when/how:
(1)对象被创建:a = 14
(2)对象被引用: b = a
(3)对象被作为参数传入到函数中:func(a)
(4)对象作为一个元素,存储在容器中(列表、数组等): mylist = [a, b, 3]
3、引用计数减1的 *** 作/when/how:
(1)对象的别名(变量名)被显示销毁: del a
(2)对象的变量名(引别名)被赋予了新的对象: a = 250
(3)对象离开了它的作用域。例如某个函数执行完毕后,该函数内的局部变量的引用计数就会减去1。
(4)该元素从容器中删除或者容器本身被销毁时。
4、引用计数的优缺点/why:
优点:
(1)简单
(2)实时性高。只要该对象的计数器为0,该对象所占用的内存就会被当作垃圾进行回收了。
缺点:
(1)维护引用计数器需要额外的内存,而且这是一笔不小的开销。
(2)容器间会存在循环引用。循环引用如果不处理好,会引发内存泄露。所以后面引入了“标记清除”
(3)每次进行内存回收,都要把所有的对象的引用计数遍历一次。这非常消耗时间,所以后面引入了“分代回收”。

标记清除

1、定义:当应用程序可用的内存空间被耗尽时,就会停止整个程序,然后进行两项工作:标记和清除。
2、标记:遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象(堆区的内存)标记为存活的对象,其余的标记为非存活对象,就是unreachable对象(不可到达对象)。说的再通俗点就是:如果通过栈区能访问到的堆区对象,那么该堆区对象不需要被标记。否则该堆区对象就是非存活对象。
3、清除:遍历标记后的堆区,将没有被标记的堆区对象全部清除掉(回收)
4、优缺点:
优点/作用:解决循环引用带来的内存泄露
缺点:暂时没有想到。

分代回收

1、分代的定义:
(1)核心定义:经过引用计数的遍历扫描扫描之后,该对象依然没有被回收。那么python的gc就会认为该对象比较经常被使用,应该降低扫描的频率。
(2)具体解释:是一种以空间换时间的 *** 作方式。Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了三个generation(代),分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在第 0 代,年轻代链表的总数达到设定阈值时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
(3)形象化解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g2MXO01u-1640616720553)(Python的垃圾回收与内存泄露_files/80267176.png)]

2、回收:依然采用引用计数是否为0作为回收的依据
3、优缺点:
优点:解决了引用计数机制导致的不停对变量进行全体扫描,可以提高垃圾回收的效率
缺点:不够实时性。假设一个变量刚从0代转为1代之后,该变量的绑定关系就被解除了,此时应该立即回收该变量。

总结

三大垃圾回收方法是由Python内部的gc自动定时管理的。

内存泄露 定义

被使用的内存无法被回收,成为不可回收的对象(uncollectable)

原因

也许你很好奇。前面不是已经介绍了Python的3大垃圾自动回收机制了,为什么还会发生内存泄露呢?
(1)所用到的用 C 语言开发的底层模块中出现了内存泄露
(2)代码中用到了全局的 list、 dict 或其它容器,不停的往这些容器中插入对象,而忘记了在使用完之后进行删除回收
(3)在循环引用中,对于unreachable,但是可以collectable的对象,Python的gc的标记清除法会自动将其回收。
但是一但对象定义了__del__方法,那么该对象就会变成uncollectable对象,Python的三大gc回收机制也回力无天了。
我去:你不定义__del__方法不就好了吗?????搞什么飞机啊???

解决方法

(1)对于(1)原因,无解
(2)对于(2)原因,删除对象就可以了
(3)对于(3)原因,搞不懂!

定位内存泄露的位置

1、定位内存泄露主要依赖于两个包:gc和objgraph。其中gc是最基础的包,objgraph是在gc的基础功能之上进行。参考官方文档:https://mg.pov.lt/objgraph/
其它类似工具:heapy、 Dozer、tarcemalloc(追踪内存分配)
2、整体思路是什么??已经在线上的代码要怎么定位内存泄露的位置?

参考

[1]https://blog.csdn.net/weixin_44571270/article/details/105775608?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164050540016780366532359%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164050540016780366532359&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-105775608.first_rank_v2_pc_rank_v29&utm_term=python%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%96%B9%E6%A1%88&spm=1018.2226.3001.4187
[2]https://blog.csdn.net/fragmentalice/article/details/84983516?ops_request_misc=&request_id=&biz_id=102&utm_term=python%E7%9A%84%E5%9E%83%E5%9C%BE%E9%83%BD%E8%87%AA%E5%8A%A8%E5%9B%9E%E6%94%B6%E4%BA%86&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-84983516.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187
[3]https://blog.csdn.net/weixin_39674190/article/details/110343447

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5681611.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存