python学习笔记之内存管理

引用计数

在python中,使用引用计数进行内存管理,一个对象没被一个变量指向,则引用计数+1,当引用计数达到最小时(一般是0),该对象便会被回收。使用sys.getrefcount可以查看一个对象的引用计数,另外其显示的引用计数会比实际引用计数多1,因为sys.getrefcount也会引用该对象。

import sys
class Person(object):
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("%s 执行析构函数" %self.name)

p1 = Person('p1')
print(sys.getrefcount(p1))
#2
p2 = p1
#3
print(sys.getrefcount(p1))
del p1
del p2
#p1 执行析构函数
print('end')

引用计数+1的情况:
1.对象被创建并被一个对象引用,如a = 23
2.对象被另外一个变量引用,如b = a
3.对象作为参数被传给函数,如fun(a)
4.对象被添加进容器中,如列表,元组,字典等
引用计数-1的情况:
1.引用这个对象的变量被删除了,如del a
2.引用这个对象的变量指向了其他的对象,如a = 24
3.函数作用域执行完毕后,如函数中的一个临时变量,在函数结束后就会消失
4.对象所在的容器被销毁,或者从容器中删除了这个对象

循环引用

在python版本中,当发生循环引用的时,即存在对象a指向对象b,对象b又指向对象a,此时引用计数机制便会失效,如下图所示是一个类似于双向链表的结构,当p1和p2被删除,但是由于两个对象还有互相引用,所以这两个对象不会被回收,我们可以写一个代码以更直观的理解。

class current(object):
    def __init__(self,name):
        self.name = name
        self.next = None
        self.prev = None
    def __del__(self):
        print("%s 执行析构函数" %self.name)
c1 = current('c1')
c2 = current('c2')
c1.next = c2
c2.prev = c1
del c1
del c2
print('end')
#先显示end,后显示c1、c2执行析构函数,这说明由于c1和c2发生了循环引用,删除引用它们的对象后他们并不会被回收

标记清除和分代回收

为了解决循环应用导致对象无法回收的问题,python引入了标记清除和分代回收机制,python会自动的创建一个叫做零代链表的链表,每有一个对象被创建,都会被链接到零代链表的尾端,并且零代链表维护着一下几个值:
1.创建的对象数
2.释放的对象数
3.轮训零代链表的次数
当创建的对象数-释放的对象数 >= 700的时候python便会轮询零代链表,找到被循环引用的对象,并将这些对象的引用计数减去1(标记清除),若此时对象的引用计数为0,则将该对象回收,完成遍历后将所有未被回收的对象加入一代链表,根据弱代假说,这些对象存活的时间较长,不需要每次都进行遍历。
在python中每遍历10次零代链表之后才会遍历一次一代链表,通过标记清除原则进行对象的回收,并将未被回收的对象加入二代链表,同理每遍历10次一代链表才会遍历一次二代链表。

常用gc模块

1.gc.set_debug(flag) 设置gc的debug日志,一般是gc.DEBUG_LEAK,可以用来查看内存泄漏的对象
2.gc.collect()手动进行垃圾回收,参数0会遍历零代链表,1会遍历零代和一代链表,2会遍历所有链表,默认参数为2
3.gc.get_threshold() 获取执行垃圾回收的阈值,返回的是一个元组,分别是零代一代和二代阈值
4.gc.set_threshold() 设置阈值
5.gc.get_count() 返回当前的计数,分别是零代链表中创建数与释放数的插值,遍历零代链表的次数和遍历一代链表的次数
下面是一个小案例

import gc

class current(object):
    def __init__(self,name):
        self.name = name
        self.next = None
        self.prev = None
    def __del__(self):
        print("%s 执行析构函数" %self.name)

print(gc.get_threshold())
#(700,10,10)
gc.set_threshold(500,8,8)
print(gc.get_threshold())
#(500,8,8)
c1 = current('c1')
c2 = current('c2')
#进行循环引用
c1.next = c2
c2.prev = c1
del c1
del c2
gc.collect()
#将循环引用的对象释放

print('end')
发布了14 篇原创文章 · 获赞 0 · 访问量 240

猜你喜欢

转载自blog.csdn.net/pnd237/article/details/103944600