浅谈Python内存管理和垃圾回收

Python使用两个主要机制来管理内存和进行垃圾回收:

  1. 引用计数
  2. 垃圾回收器(主要用于处理引用循环)

我们先简要讨论这两种机制,然后详细说明如何处理引用循环。

1. 引用计数

每个Python对象都有一个关联的引用计数,该计数表示有多少引用指向该对象。当创建一个新的引用(例如,赋值操作)时,引用计数增加。当引用超出范围或被重新分配时,引用计数减少。当对象的引用计数达到0时,该对象的内存可以立即被回收。

但是,引用计数方法有一个主要的问题:它不能处理引用循环。考虑以下例子:

a = []
b = []
a.append(b)
b.append(a)

这里,ab相互引用,但当我们删除对它们的引用后:

del a
del b

尽管ab都不再被访问,但它们的引用计数仍然大于0(因为它们彼此持有引用)。这会导致内存泄漏,除非有其他方式来识别和处理这种情况。

2. 垃圾回收器

为了解决引用循环问题,Python还提供了一个垃圾回收器(GC),其基于“分代回收”策略。这个垃圾回收器的主要任务是检测引用循环并回收它们。

Python的垃圾回收器将所有对象分为三代(第0代、第1代和第2代)。新创建的对象放在第0代。如果一个对象在第0代的垃圾回收过程中经历了并且存活下来,它将被移动到第1代。同样,从第1代存活到第2代的对象会被移动到第2代。

GC的工作流程是这样的:

  1. 首先,它定期执行,特别是当第0代对象的数量超过某个阈值时。
  2. 它会查找引用循环。为此,GC实际上不考虑所有对象,而只考虑可能导致引用循环的容器对象(例如列表、字典、类实例等)。
  3. 当GC找到这样的循环并确定循环中的对象都不可达时,它会清除这些对象并释放它们的内存。

使用gc模块,可以与垃圾回收器进行交互,手动触发垃圾回收、调查不可达的对象、禁用或启用垃圾回收等。

总之,尽管引用计数是Python内存管理的主要机制,但为了处理引用循环,垃圾回收器是必不可少的。

GC如何确定循环中的对象都不可达?

确定对象不可达的过程可以被视为两个主要步骤:标记和清扫。在Python中,垃圾回收器使用一个稍微不同但基于同样原理的方法来处理引用循环和不可达对象。

  1. 建立对象图
    为了确定哪些对象是不可达的,垃圾回收器首先查看所有的容器对象,构建一个对象图。在这个图中,节点代表对象,边表示一个对象引用另一个对象。

  2. 找出所有根对象
    “根”对象通常是那些可以从Python程序访问的对象,例如全局变量、活跃的函数堆栈中的变量等。这些对象被视为可访问的,因此它们及其引用的所有对象也都是可访问的。

  3. 遍历所有可访问对象
    从根对象开始,垃圾回收器遍历整个对象图,标记所有可以访问的对象。

  4. 处理不可达对象
    遍历结束后,未被标记的对象即为不可达对象。这意味着,从根对象出发,没有办法通过引用链来访问这些对象。因此,这些对象(及其关联的引用循环)是安全的、可以被清理的目标。

  5. 清理
    一旦确定了哪些对象是不可达的,垃圾回收器就会清理这些对象并释放与它们相关的内存。

值得注意的是,Python的垃圾回收器在处理不可达对象时还需要考虑其他因素,例如对象的__del__方法。如果对象定义了这个方法,清理过程会更复杂,因为__del__方法可能会再次使对象变得可达。

最后,要强调的是,尽管垃圾回收器帮助处理引用循环,但编写清晰、可维护的代码仍然是避免内存泄漏的最佳方法。确保资源得到适当的清理,并避免不必要的全局引用或长时间存活的对象,都是良好的编程实践。

Q1:__del__方法可能会再次使对象变得可达。为什么?

A1:在Python中,__del__方法被称为对象的“析构函数”或“终结器”。当对象的引用计数降至零时,或者它被垃圾回收器标记为不可达时,__del__方法会被自动调用。这为开发者提供了在对象被销毁之前执行一些清理工作的机会,例如关闭文件、断开网络连接等。

但是,__del__方法中的代码完全由开发者控制,并且可以执行任意的Python代码,包括重新为对象创建引用。这就意味着,在对象的__del__方法中,可以执行使对象重新变得可达的操作。这可能会导致预期的垃圾回收不会发生。

来看一个例子:

class SomeClass:
    instance = None

    def __del__(self):
        SomeClass.instance = self
        print("Deleted!")

obj = SomeClass()
del obj

# At this point, __del__ is called. But instead of allowing the object to be collected,
# the __del__ method reassigns a reference to the object to the class variable 'instance'.

在上述代码中,当我们尝试使用del语句删除obj引用时,__del__方法会被调用。但是,在__del__方法中,我们重新为对象创建了一个引用(通过SomeClass.instance)。这意味着对象不会被真正的销毁,因为它又再次变得可达。

由于这种行为可能导致难以预测的结果和潜在的内存泄漏,所以应当谨慎使用__del__方法,并尽量避免在其中执行复杂的操作或重新为对象创建引用。

】:上面的程序中SomeClass.instance = self 这行代码在Python中执行了两个主要操作:

  1. self引用

    在Python类的实例方法中,self是一个对当前对象实例的引用。所以,当我们在SomeClass的方法内部使用self时,我们实际上是在引用或操作调用该方法的对象实例。

  2. 赋值给类变量

    SomeClass.instance 是对类级别变量(也称为类属性或类变量)instance的引用。当我们写 SomeClass.instance = self,我们实际上是将当前对象的引用赋给了类的instance属性。这意味着,即使所有外部对该对象的引用都被删除,我们仍然可以通过SomeClass.instance来访问这个对象。

这行代码的主要效果是将当前对象重新与类的静态变量instance关联,从而确保它在__del__方法执行后仍然是可达的。这也是为什么在之前的示例中,尽管尝试了删除对象的引用,但对象实际上并没有被销毁的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青衫客36

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值