python3内存管理机制_Python 内存管理机制

本文详细介绍了Python3的内存管理机制,包括引用计数器如何工作,以及如何处理循环引用的问题。当对象的引用计数为0时,对象会被释放。然而,循环引用会导致对象无法释放,为了解决这一问题,Python引入了垃圾回收机制,通过分代回收和双向链表检测循环引用。此外,文章还讨论了垃圾回收的触发条件、分代回收的优化策略以及手动和自动回收的方法。通过设置gc模块的阈值和使用弱引用,可以有效地管理内存并避免内存泄漏。
摘要由CSDN通过智能技术生成

本文根据 撩课-Python内存管理机制 整理而成。python

引用计数器机制

当一个对象被引用时,引用计数 +1,当这个对象再也不被引用,或引用它的对象被释放时,引用计数 -1,当对象的引用计数为 0 时,释放该对象。函数

使用 sys.getrefcount(obj) 能够查看一个对象的当前引用计数。在 Python 中,当对象被传入到一个函数时,在这个函数的内部有会两个对象引用着它。可是 sys.getrefcount(obj) 比较特殊,一般只引用一次。性能

class Person:

pass

def log(obj):

# obj += 2

print(sys.getrefcount(obj)) # obj += 1

p = Person() # p = 1

log(p) # p = 4

print(sys.getrefcount(obj)) # p = 2

复制代码

对象在离开函数做用域时,会断开和函数对象之间的引用,所以最后 p 的引用计数为 2。测试

循环引用

简单来讲,当一个对象再也不使用时,应该被释放,可是,当对象被删除后仍然存在引用计数时,将没法释放该对象。spa

class Person:

def __del__(self):

print("Person({0}) 被释放".format(id(self)))

class Dog:

def __del__(self):

print("Dog({0}) 被释放".format(id(self)))

p = Person() # p = 1

dog = Dog() # dog = 1

# 循环引用

p.pet = dog # dog = 2

dog.master = p # p = 2

# 程序结束前 __del__() 不被调用

# 因为循环引用,本质上没法真正删除 p, dog,只是在语法层面上删除了它们。

del p, dog # p, dog = 1, 1

复制代码

在语法层面上,p 、dog 被删除后就没法再使用了,也没法经过 p 、dog 的属性 pet 和 master 来找到它们。 所以,将 p 、dog 称之为 可到达的引用,将 pet 、master 称为 不可到达的引用。也就是说,将 p 、dog 删除后,虽然 pet 和 master 所引用的 dog 、p 还在内存中,可是已经没法经过正常手段来访问他们了,p 、dog 对象将在内存中没法被释放掉。设计

当被 del 后的对象还存在引用计数时,经过 引用计数器机制 就没法作到真正从内存中回收它们,因而就形成了,由循环引用引发的内存泄漏问题。3d

"""

错误!未定义 p, dog

print(p)

print(dog)

"""

复制代码

垃圾回收机制

Python 由两套内存管理机制并存,分别是 引用计数器机制 和 垃圾回收机制。引用计数器机制性能优于垃圾回收机制,可是没法回收循环引用。所以,垃圾回收机制的主要做用在于,从 经历过引用计数器机制后 仍未被释放的对象中,找到循环引用并释放掉相关对象。code

垃圾回收的底层机制(如何找到循环引用?)orm

收集全部 容器对象 ( list , dict , tuple , customClass, ... ) ,经过一个双向链表进行引用;

针对每个容器对象,经过一个变量 gc_refs 来记录当前对应的引用计数;

对于每一个容器对象,找到它所引用的容器对象,并将这个容器对象的引用计数 -1;

通过步骤 3 后,若是一个容器对象的引用计数为 0,就表明这个对象能够被回收了,确定是 "循环引用" 才致使它活到如今的。

分代回收(如何提高查找循环引用的性能?)htm

若是程序中建立了不少个对象,而针对每个对象都要参与 检测 过程,则会很是的耗费性能,基于这个问题,Python 提出了一个假设,那就是:越命大的对象越长寿。

假设一个对象被检测 10 次都没有把它释放掉,就认定它必定很长寿,就减小对这个对象的 检测频率。

分代检测(基于假设设计出的一套检测机制)

默认一个对象被建立出来后,属于第 0 代;

若是经历过这一代 垃圾回收 后,依然存活,则划分到下一代;

垃圾回收的周期顺序

0 代 "垃圾回收" 必定次数后,触发 0~1 代回收;

1 代 "垃圾回收" 必定次数后,触发 0~2 代回收。

关于分代回收机制,它主要的做用是能够减小垃圾检测的频率。严格来讲,除了它有这个机制限定外,还有一个限定它的条件,那就是,在 垃圾回收器 中,当 "新增的对象个数 - 销毁的对象个数 = 规定阈值" 时才会去检测。

触发垃圾回收

自动回收

触发条件是,开启垃圾回收机制 ( 默认开启 ),而且达到了垃圾回收的阈值。

须要注意的是,触发并非检查全部的对象,而是分代回收。

手动回收 ( 默认0~2 )

只需执行 gc.collect(n) ,n 能够是 0~2,表示回收 0~n 代垃圾。

gc 模块

gc 模块能够查看或修改 垃圾回收器 当中的一些信息。

import gc

复制代码

gc.isenabled()

判断垃圾回收器机制是否开启。

gc.enable()

开启垃圾回收器机制 ( 默认开启 ) 。

gc.disable()

关闭垃圾回收器机制。

gc.get_threshold()

获取触发执行垃圾检测阈值,返回值是一个元组 ( threshold, n1, n2 ) 。

threshold

就是触执行发垃圾检测的阈值,当 新增的对象个数 - 销毁的对象个数 = threshold 时,执行一次垃圾检测。

n1

表示当 0 代垃圾检测达到 n1 次时,触发 0~1 代垃圾回收。

n2

表示当 1 代垃圾检测达到 n2 次时,触发 1~2 代垃圾回收。

gc.set_threshold(1000, 15, 15)

修改垃圾检测频率。通常状况下,为了程序性能,会把这些数值调大。

测试自动回收 1

import gc

# "建立对象的次数 - 销毁对象的次数 = 2" 时,触发自动回收。

gc.set_threshold(2, 10, 10)

class Person:

def __del__(self):

print(self, "被释放")

class Dog:

def __del__(self):

print(self, "被释放")

p = Person() # p = 1

dog = Dog() # dog = 1

# 循环引用

p.pet = dog # dog = 2

dog.master = p # p = 2

# 多建立一个 Person 类,目的是为测试在删除对象后,程序可以触发自动回收。

p2 = Person()

# 程序结束前,不调用 __del__()。

del p

del dog

复制代码

总共建立 3 个对象,销毁了 1 个对象,3-1=2。理论上说,此时应该触发自动回收,但直到程序结束以前,__del__() 函数都没有被调用,这是为何呢?

要解释这个问题,首先就要了解,为何垃圾检测会存在 "新增的对象个数 - 销毁的对象个数 = 规定阈值" 这样一个限定条件。

这是由于,当对象遗留在内存中没法被释放时,缘由一般是对象建立多了而没有被及时销毁的缘由。

那么根据这个结论,就能够设定一个机制,当 "建立的对象" 多出 "被销毁的对象" 大于或等于 "指定阈值" 时,再让程序去检测垃圾回收,不然不触发检测。

在销毁一个对象时,表现的是,将减小一次达到指定阈值的条件,也就没有必要再去检测了。

因此严格来讲,这个限定条件要改为:在建立对象时,"新增的对象个数 - 销毁的对象个数 = 规定阈值" 时 ,触发垃圾检测。

了解了这些以后,你就知道,为何这里对象没法被释放了。首先建立了 3 个对象,而后执行 del p 、del dog,而在执行销毁操做时,是不会触发垃圾检测的,所以对象不被释放。

注意

此结论是我我的推测的,也有可能真是状况并非这样。我也是想了很久为何不释放对象,最终想到的一个比较合理的解释。

测试自动回收 2

import gc

gc.set_threshold(2, 10, 10)

class Person:

def __del__(self):

print(self, "被释放")

class Dog:

def __del__(self):

print(self, "被释放")

p = Person() # p = 1

dog = Dog() # dog = 1

# 循环引用

p.pet = dog # dog = 2

dog.master = p # p = 2

# 尝试在删除 "可到达引用" 后,真实对象是否有被回收。

del p, dog

# 多建立一个 Person 类,目的是为测试在删除对象后,程序可以触发自动回收。

p2 = Person()

print("p2 =", p2)

print("----------------------- 程序结束 -----------------------")

"""

<__main__.person object at> 被释放

<__main__.dog object at> 被释放

p2 = <__main__.person object at>

----------------------- 程序结束 -----------------------

<__main__.person object at> 被释放

"""

复制代码

总共建立 5 个对象,销毁了 3 个对象,5-3=2,触发自动检测。此时发现 p , g 已被销毁 ( 真实对象还在内存中 ),因而找到它们所引用的对象,将计数 -1,p 、dog 得以被释放。

注意:是 p 、dog 先被释放,p2 在程序结束后被释放。

手动回收

import gc

class Person:

def __del__(self):

print(self, "被释放")

class Dog:

def __del__(self):

print(self, "被释放")

p = Person() # p = 1

dog = Dog() # dog = 1

# 循环引用

p.pet = dog # dog = 2

dog.master = p # p = 2

del p # p = 1

del dog # dog = 1

# 对程序执行垃圾检测 (无关回收机制是否开启),手动回收内存。

gc.collect()

# <__main__.person object at> 被释放

# <__main__.dog object at> 被释放

复制代码

弱引用

import weakref

import sys

class Person:

def __del__(self):

print(self, "被释放")

class Dog:

def __del__(self):

print(self, "被释放")

p = Person() # p = 1

dog = Dog() # dog = 1

p.pet = dog # dog = 2

# weakref.ref 不强引用指定对象 (即不增长引用计数)。

dog.master = weakref.ref(p) # p = 1

# p 被彻底销毁时,它所引用对象的计数 -1.

del p # p = 0, dog = 1

del dog # dog = 0

# <__main__.person object at> 被释放

# <__main__.dog object at> 被释放

复制代码

为证实一个对象被销毁时,它所引用对象的计数是否 -1,特此作个实验,来观察 p 被销毁时,它所指向的 dog 引用计数。

p.pet = dog # dog = 2

dog.master = weakref.ref(p) # p = 1

del p # p = 0, dog = 1

"""

观察 p 被销毁时,它所引用的 dog 计数是否被 -1

sys.getrefcount 用于获取一个对象的当前引用计数,返回值比实际值多 1。

"""

print(sys.getrefcount(dog)) # 2

del dog # dog = 0

复制代码

当 p 被销毁时,意味着在 p.pet = god 这条语句中,前面的 p 、p.pet 已经不存在了,只剩下 = dog ,前面空空如也,并不被任何对象所引用,所以 dog 的引用计数 -1。

而在强引用下,p 被销毁时,dog 的引用计数不变。

p.pet = dog # dog = 2

dog.master = p # p = 2

del p # p = 1, dog = 2

print(sys.getrefcount(dog)) # 3,实际值为 2.

del dog # dog = 1

复制代码

要在一个集合中弱引用对象,使用 weakref.Weak... 。

# 弱所引用字典中的对象

# pets = weakref.WeakValueDictionary({"dog": d1, "cat": c1})

复制代码

手动打破循环引用

class Person:

def __del__(self):

print(self, "被释放")

class Dog:

def __del__(self):

print(self, "被释放")

p = Person() # p = 1

dog = Dog() # dog = 1

p.pet = dog # dog = 2

dog.master = p # p = 2

"""

在删除前手动打破循环引用

这意味着手动断开 p.pet 与 dog 之间的引用,

当 dog 再也不被 p 引用时,计数天然 -1。

"""

p.pet = None

del p # p = 0, dog = 1

del dog # dog = 0

复制代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值