内存管理

标记清除和分代回收

Python程序中,每次你新创建了一个对象,那么就会将这个对象挂到一个叫做零代链表中(当然这个链表是Python内部的,Python开发者是没法访问到的)。比如现在你在程序中创建四个Person对象,分别叫做p1p2p3以及p4,然后p1p2之间互相引用,并且让p3p4的引用计数为2

import sys

class Person(object):
    def __init__(self,name):
        self.name = name
        self.next = None
        self.prev = None

p1 = Person('p1')
p2 = Person('p2')
p3 = Person('p3')
p4 = Person('p4')

p1.next = p2
p2.prev = p1

temp1 = p3
temp2 = p4

print(sys.getrefcount(p1))
print(sys.getrefcount(p2))
print(sys.getrefcount(p3))
print(sys.getrefcount(p4))

以上代码实际上就会将p1p2以及p3p4挂在一个叫做零代链表
零代链表

这时候p1引用了p2,而p2又引用了p1,因此这两个对象产生了循环引用。在后期即使删除了del p1以及del p2,那么这两个对象也会得不到释放。
del后的零代链表

因此这时候Python就启用了一个新的垃圾回收的机制。如果创建的对象总和减去被释放的对象,达到一定的值(某个阈值 > 700),那么Python就会遍历这个零代链表,找到那些有相互引用的对象,将这些对象的引用计数减1,如果引用计数值为0了,那么就说明这个对象是可以被释放的,比如p1p2,这时候就会释放p1p2。接下来再将没有被释放的对象,挪动到一个新的链表中,这个链表叫做一代链表
一代链表

gc模块

Python中的gc模块封装了许多和对象以及垃圾回收相关的方法。

导致引用计数+1的情况:

  • 对象被创建,并被一个对象所引用。例如a=23
  • 对象被另外一个变量引用。例如b=a
  • 对象被作为参数传递给函数。例如func(a)
  • 对象被添加到容器中,比如添加到列表、元组、字典、集合中等。例如temp=[a]

导致引用计数-1的情况:

  • 引用这个对象的变量被删掉了。例如del a
  • 引用这个对象的变量指向其他的对象了。例如a='abc'
  • 函数作用域执行完毕后。比如一个函数中的临时变量,在这个函数执行结束后就会消失。
  • 对象所在的这个容器被销毁,或者从这个容器中删除了这个对象,也会导致这个对象引用计数减1。

查看一个对象的引用计数:

import sys
a = "hello world"
print(sys.getrefcount(a))

打印出来的引用计数,总是会比真实的引用计数多1,原因是因为将这个对象传给了getrefcount函数,这个过程会给这个对象的引用计数加1.

gc模块常用函数:

  1. gc.set_debug(flags):设置gcdebug日志,一般设置为gc.DEBUG_LEAK可以看到内存泄漏的对象。
  2. gc.collect(generation):执行垃圾回收。会将那些有循环引用的对象给回收了。这个函数可以传递参数,0代表只回收第0代的的垃圾对象、1代表回收第0代第1代的对象,2代表回收第0、1、2代的对象。如果不传参数,那么会使用2作为默认参数。
  3. gc.get_threshold():获取gc模块执行垃圾回收的阈值。返回的是个元组,第0个是零代的阈值,第1个是0代的遍历次数阈值,第2个是1代的遍历次数阈值
  4. gc.set_threshold():设置执行垃圾回收的阈值。
  5. gc.get_count():获取当前自动执行垃圾回收的计数器。返回一个元组。第0个是零代的垃圾对象的数量,第1个是当前零代链表遍历的次数,第2个是当前1代链表遍历的次数。

关于阈值和垃圾回收:

假设通过gc.get_threshold()返回的是(700,10,10),那么意味着只要零代垃圾值到了700,就会执行gc.collect(0)(即遍历一次),回收零代的垃圾值;只要1代链表遍历了10次,就会执行gc.collect(1)(即遍历一次),回收零代1代的垃圾值。只要2代链表遍历了10次,就会执行gc.collect(2),回收零代1代以及2代的垃圾值。

注意点:

gc模块不能处理的是,如果两个循环引用的对象都实现了__del__方法(定义类时,自定义了__del__方法),那么将不会进行垃圾回收,因此尽量不要在类中实现自己的__del__方法。否则发生循环引用后就会产生内存泄露(对象无法得到释放)。

import gc


class Person(object):
    def __init__(self,name):
        self.name = name
        self.pointer = None

    def __del__(self):
        print('%s 被回收了' % self.name)


# print(gc.get_threshold())
# gc.set_threshold(500)
# print(gc.get_threshold())

gc.set_debug(gc.DEBUG_LEAK)

while True:
    print(gc.get_count())
    p1 = Person('p1')
    p2 = Person('p2')

    # 产生循环引用
    p1.pointer = p2
    p2.pointer = p1

    del p1
    del p2

    a = input('test:')
    gc.collect(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值