python垃圾回收 (GC) 机制

Python 能够自动进行内存分配和释放,但了解 python 垃圾回收 (garbage collection, GC) 的工作原理可以帮助你写出更好更快的 Python 程序。Python 使用两种算法进行垃圾回收,分别是引用计数 (Reference Counting)分代回收 (Generational garbage collection)

引用计数

引用计数,简而言之就是如果没有变量引用某一对象,那么该对象将会被回收。Python 中的每个变量都是对对象的引用,而不是对象本身。例如,赋值语句只是给右侧对象或右侧变量所对应的对象建立一个引用;一个对象都可以有许多引用。

核心概念:变量是指向一个对象的指针;有n个变量指向某一个对象,那该对象的引用计数则为n,又称该对象有n个引用

import sys
a = [1, 2, 3]
b = a # 赋值操作本身不会对数据进行复制,仅仅是建立引用关系print(id(a), id(b))  # 4483101696 4483101696,变量a b的id相同,说明a b指向同一对象
print(sys.getrefcount(a))  # 3, 其中调用getrefcount函数会使引用+1

为了跟踪每个对象的引用次数,每个对象都有一个名为引用计数的额外属性,当创建或删除指向对象的指针时,该属性的值会相应的增加或减少。以下三种情况会使对象的引用次数增加:

  • 赋值运算
  • 参数传递
  • 将对象附加到容器对象中

  如果某对象引用计数属性的值为零,CPython 会自动调用该对象特定的内存释放函数。如果该对象还包含对其他对象的引用,那么所包含的其他对象的引用计数也会自动减少。因此,可以依次释放其他对象。

  值得注意的是,在函数、类和代码块(如if-else代码块)之外声明的变量称为全局变量(global variables)。通常,这些变量会一直存在直到 Python 进程结束。因此,全局变量引用的对象的引用计数永远不会下降到零。在python进程中,所有全局变量都存储在一个字典中,可以通过调用globals()函数来获取全局变量。那反过来呢?在代码块内(例如,在函数或类中)定义的变量则具有一个局部作用域,可以通过调用locals()函数来获取局部变量。当 Python 解释器执行完一个代码块时,它会破坏在块内创建的局部变量及其引用。

我们举个例子:

import sysfoo = []
print(sys.getrefcount(foo)) # 2 references, 1 from the foo var and 1 from getrefcountdef bar(a):print(sys.getrefcount(a))bar(foo) # 4 references, from the foo var, function argument, getrefcount and Python's function stack
print(sys.getrefcount(foo)) # 2 references, the function scope is destroyed

当你想删除全局或局部变量时,可以使用删除变量及其引用(而不是对象本身)的 del语句。这在 jupyter notebook中工作时通常很有用,因为在jupyter notebook中所有单元格变量都是全局变量。CPython 使用引用计数的主要原因是历史原因,现在有很多关于这种技术的弱点的争论。比如,有人认为现代的垃圾回收算法可以更高效,无需使用引用计数。引用计数算法存在很多问题,例如循环引用、线程锁定以及额外内存和性能开销。必须指出的是,引用计数是 Python 无法摆脱全局解释锁 (GIL) 的原因之一。

分代回收

上面讲到了引用计数的缺点包括循环引用、线程锁定以及额外内存和性能开销。线程锁定,所以在python中使用多线程;额外内存和性能开销,我们也认了;但是循环引用的问题不解决的话,就会造成内存泄露问题。因此,python引入了分代回收的算法专门来解决循环引用的问题。

引用计数算法非常有效和直接,但它无法检测循环引用,所以python在引用计数的基础上,还需要分代回收。引用计数是 Python必需的功能,不能禁用;而分代回收是可选的,可以手动设置。

在这里插入图片描述

如上图示例所示,lst对象指向自身,而且Object 1Object 2 相互引用。在这两种情况下,这些对象的引用数永远至少为1。我们可以用代码来演示一下:

import gc
import sys
import ctypes# 通过内存地址去访问没有引用的对象(unreachable objects)
class PyObject(ctypes.Structure):_fields_ = [("refcnt", ctypes.c_long)]gc.disable()  # 禁用分代回收算法
lst = []
lst.append(lst)
lst_address = id(lst)
del lstobject_1, object_2 = {}, {}object_1['obj2'] = object_2
object_2['obj1'] = object_1
obj_address = id(object_1)
del object_1, object_2# 手动对象回收
# gc.collect()# 获取对象引用数量
print(PyObject.from_address(obj_address).refcnt)
print(PyObject.from_address(lst_address).refcnt)# 或者通过以下方式获取引用数量
# tmp = PyObject.from_address(obj_address)
# print(sys.getrefcount(tmp))# # output
1
1
2

在上面的示例中,del语句删除了对我们对象的引用(引用计数减 1)。 Python 执行 del语句后,我们的对象不再可以从 Python 代码访问。但是,这些对象仍然存在于内存中。发生这种情况是因为它们仍在相互引用,并且每个对象的引用计数为 1。因为我们前面通过gc.disable()禁用了分代回收,因为循环引用对象无法释放;这时,我们可以通过调用gc.collect()手动触发对象回收。

  我们知道,在python中对象分为可变对象和不可变对象。不可变对象包括int, float, complex, strings, bytes, tuple, rangefrozenset;可变对象包括list, dict, bytearrayset。循环引用仅存在于container对象(比如,list, dict, classes, tuples),python垃圾回收算法主要追踪可变对象及不可变对象tuple。如果tuple, dict包含的元素都是不可变对象,那么回收算法可以不对该对象进行追踪。

垃圾回收的触发

不同于引用计数,循环引用的垃圾回收不是实时作用的,而是定期运行。垃圾回收器将container对象分成三代(0, 1, 2),每个新对象都从第一代开始。如果一个对象在一个垃圾回收轮次中幸存下来,它将移至较旧(更高)的一代。较低代的回收频率高于较高代,因为大多数新对象往往会被先销毁。这样分代回收的策略能提高性能并减少垃圾回收带来的暂停时间。

  为了决定何时进行一轮垃圾回收,每一代都有一个单独的计数器和阈值。计数器存储自上次收集以来的对象分配数减去释放数的差值。每次分配新的容器对象时,CPython 都会检查第0代的计数器是否超过阈值(通过gc.get_count()获得三代对象计数器存储的数值)。如果超过阈值,Python 将触发垃圾回收。我们可以通过gc.get_threshold()gc.set_threshold()查看、设置阈值:

import gc
gc.get_threshold()  # (700, 10, 10) 分别对应三代计数器的阈值
gc.set_threshold(threshold0=800, threshold0=10, threshold0=10)  # 当threshold0设置为0时,禁用循环GC

  在写程序时,可以通过将调试标志设置为gc.DEBUG_SAVEALL,从而将所有unreachable对象添加到gc.garbage 中,帮助提升程序质量:

import gcgc.set_debug(gc.DEBUG_SAVEALL)print(gc.get_count())
lst = []
lst.append(lst)
list_id = id(lst)
del lst
gc.collect()
for item in gc.garbage:print(item)

参考

python documentation: GC

Garbage collection in Python: things you need to know

stacloverflow reference count

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/51511.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

经典贪心问题之圣诞老人的礼物

目录 题目信息: 题目分析: 题解代码: 题目信息: 注:4指一共有四箱糖果,15指雪橇共能带走的最大重量为15,接下来输出4行,每行两个数据, 第一个数据指这箱糖果的价值&a…

【Python】《点燃我,温暖你》,快来Get李峋同款爱心代码

前言 hello,大家好 最近有个剧挺火的 就是那个程序员的剧,叫《点燃我,温暖你》 最近听说很火呀,那作为程序员,Python中的战斗机的小编,能不给大家安排一波! 怎么说呢,用这个表白也可…

隔壁老王出喝酒去了,留下女友半夜一个人在家,我用python给她写了一个

哈喽大兄弟们 今日重点: 划重点: 1、python开发小游戏,pygame环境搭建; 2、给失眠的小姐姐开发一个迷宫小游戏。 代码干货满满,建议收藏实操!!!有问题及需要,请留言哦~~ …

程序员用10分钟写了个旅游管家APP,女友用了直呼贴心

「呐,你知道吗? 听说樱花飘落的速度是秒速五厘米哦。」 听到阿珍又念起这句经典台词,阿强,这个对自然界的花期不太敏感的程序员,也收到了“樱花开了”的讯号。 春天的樱花不能错过,赏樱是写进阿珍价值观的…

python实现垃圾分类程序,对于要处理的垃圾,判断该投放到哪个类别的垃圾桶中

python实现垃圾分类程序,对于要处理的垃圾,判断该投放到哪个类别的垃圾桶中 一、编程题目 编程题目:输入要处理的垃圾,空格分隔,判断并输出各个垃圾应该投放到哪个类别的垃圾桶中。其中垃圾有以下分类: 废…

Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解

Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。引用计数最大缺陷就是循环引用的问题,所以Python采用了辅助方法。本篇文章并不详细探讨Python的垃圾回收机制的内部实现,而是以gc模块为切入点学习Python的垃圾回收机制&#x…

【PythonGUI小程序】相信我,这是最in的n种骰子梭哈小游戏新玩法,好玩到丧心病狂~(文中有惊喜)

导语 哈喽!大家晚上好,我是木木子吖,很久没给大家更新游戏代码的类型啦~ 所有文章完整的素材源码都在👇👇 粉丝白嫖源码福利,请移步至CSDN社区或文末公众hao即可免费。 在长沙这个美食遍地的城市&#xff…

李峋同款心跳Python代码

李峋同款心跳Python代码【按头安利《点燃我温暖你》】 import random from math import sin,cos,pi,log from tkinter import * CANVAS_WIDTH 640 CANVAS_HEIGHT 640 CANVAS_CENTER_X CANVAS_WIDTH / 2 CANVAS_CENTER_Y CANVAS_HEIGHT / 2 IMAGE_ENLARGE 11 HEART_COLOR …

【Python案例】Python实现垃圾分类APP(附带微信小程序)

嗨嗨,大家好呀,我是小圆~ 今天给你们分享一个有趣的东西 如何利用现有的工具来实现一个垃圾分类的应用 主要做了三个核心内容: 对比现有垃圾分类服务,挑选一个合适并编码实现开发桌面版垃圾分类APP开发垃圾分类微信小程序 上…

【python】制作李峋同款爱心代码,也不是很难嘛~

前言 嗨喽~大家好呀,这里是魔王呐 ❤ ~! 最近,一部名叫《点燃我,温暖你》得电视剧冲进了大家得视野~ 其中李峋用代码做出的红色跳动的爱心,一下子跳到朱韵的心坎里,同样也跳到我们的心坎 今天,我们就用py…

太棒了 | 手把手教你用Python做一个 “举牌小人” 生成器!

教你如何使用Selenium库 本文禁止转载,如有违反,严肃处理! 前几天写了一个婴儿级别的爬虫图文教程,大家很喜欢。戳我查看:3000字 “婴儿级” 爬虫图文教学 | 手把手教你用Python爬取 “实习网”! 趁热打铁…

李峋的跳动爱心代码(python)

运行效果 代码: """ author:Athena Geng """ import random from math import sin, cos, pi, log from tkinter import *CANVAS_WIDTH 640 # 画布的宽 CANVAS_HEIGHT 480 # 画布的高 CANVAS_CENTER_X CANVAS_WIDTH / 2 # 画布中…

李峋爱心Python代码

李峋爱心Python代码: # codinggbk import random from math import sin, cos, pi, log from tkinter import * CANVAS_WIDTH 640 CANVAS_HEIGHT 480 CANVAS_CENTER_X CANVAS_WIDTH / 2 CANVAS_CENTER_Y CANVAS_HEIGHT / 2 IMAGE_ENLARGE 11 HEART_COLOR "…

李峋同款爱心python实现

爱心运行结果截图: import random from math import sin, cos, pi, log from tkinter import *CANVAS_WIDTH 840 # 画布的宽 CANVAS_HEIGHT 680 # 画布的高 CANVAS_CENTER_X CANVAS_WIDTH / 2 # 画布中心的X轴坐标 CANVAS_CENTER_Y CANVAS_HEIGHT / 2 # 画…

OpenAI CEO创建的Worldcoin正式推出「世界币」:AI 时代的数字通行证?

编辑:秦晋 据外媒Semafor独家报道,知情人士透露,由 OpenAI 首席执行官 Sam Altman 创建的Worldcoin 代币将于今日推出。 在硅谷引发争议的Worldcoin 代币试图解决两个棘手问题:在线身份认证与收入不平等问题。 据知情人士透露&…

华为鸿蒙HarmonyOS 4定档8月;ChatGPT之父的加密货币正式上线;微软必应聊天将推出重新生成答案功能|极客头条...

「极客头条」—— 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews) 一分钟速览新闻点&#…

数据分析实战(六):英国电商用户行为分析

案例:英国电商用户行为数据分析 Part 1. 数据获取 1.1 数据集简介 https://archive.ics.uci.edu/ml/datasets/onlineretail# 该数据集为英国在线零售商在2010年12月1日至2011年12月9日间发生的所有网络交易订单信息。 1.2 数据集内容 数据集为xlsx格式&#xff0c…

亚马逊跟卖分析与经验分享

亚马逊设置跟卖机制,也是给了卖家一个机会,对于新手来说自己制作产品的listing花费大量的时间与精力大多数出单效果很不理想,这个时候把握得当跟卖就会取得很大的优势。 首先它能快速的获得流量 亚马逊的跟卖就是产品的listing共享&#xff…

Lazada和Shopee选品分析之马来西亚电商市场详解-海鲸跨境

马来西亚基础信息: 马来西亚是东南亚第三大经济体,国民富足;并且年轻人众多,对中国的产品非常喜爱。 国民经济:2019你那GDP 3543亿美元,增长4.7% 人均收入:10460美元,仅次于新加坡 年龄结构:30岁以下年轻人占人口53% 电商市场规模与潜力: 马来西亚电商用户数量…

为Lazada商家量身定做的精细化运营数据分析软件,Ushop BI

跨境电商数据可视化BI系统现在在市面上也是比较普遍,但是针对Lazada平台的目前只有一个Ushop BI系统,Ushop BI系统能够把Lazada平台数据可视化做的非常好,其功能覆盖面广,操作简单,是Lazada商家不可多得的好帮手&#…