另一篇关于java的垃圾回收机制:
Java 技术之垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。
# encoding=utf-8
import gc
import time
class ClassA():
def __init__(self):
print 'object born,id:%s' % str(hex(id(self)))
def __del__(self):
print 'object del,id:%s' % str(hex(id(self)))
def f1():
while True:
c1 = ClassA()
del c1
f1()
执行f1()会循环输出这样的结果,而且进程占用的内存基本不会变动
object born,id:0x237cf58
object del,id:0x237cf58
c1=ClassA()会创建一个对象,放在0x237cf58内存中,c1变量指向这个内存,这时候这个内存的引用计数是1
del c1后,c1变量不再指向0x237cf58内存,所以这块内存的引用计数减一,等于0,所以就销毁了这个对象,然后释放内存。
导致引用计数+1的情况
导致引用计数-1的情况
import sys
def func(c, d):
print 'in func function', sys.getrefcount(c) - 1
print 'init', sys.getrefcount(11) - 1
a = 11
print 'after a=11', sys.getrefcount(11) - 1
b = a
print 'after b=11', sys.getrefcount(11) - 1
func(11, 11)
print 'after func(a)', sys.getrefcount(11) - 1
list1 = [a, 12, 14]
print 'after list1=[a,12,14]', sys.getrefcount(11) - 1
a = 12
print 'after a=12', sys.getrefcount(11) - 1
del a
print 'after del a', sys.getrefcount(11) - 1
del b
print 'after del b', sys.getrefcount(11) - 1
# list1.pop(0)
# print 'after pop list1',sys.getrefcount(11)-1
del list1
print 'after del list1', sys.getrefcount(11) - 1
查看一个对象的引用计数
sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
def f2():
while True:
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t=c1
del c1
del c2
print '111111111111111'
print gc.garbage
print '222222222222222'
print gc.collect() # 显式执行垃圾回收
print '333333333333333'
print gc.garbage
f2()
执行f2(),进程占用的内存会不断增大。
创建了c1,c2后,0x237cf30(c1对应的内存,记为内存1),0x237cf58(c2对应的内存,记为内存2)这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.
在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
def f3():
# print gc.collect()
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t=c1
del c1
del c2
print '11'*50
print gc.garbage
print '22'*50
print gc.collect() #显式执行垃圾回收
print '33'*50
print gc.garbage
# time.sleep(10)
if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
f3()
输出:
object born,id:0x6b1af08L
gc: uncollectable 0000000006B1AF08>
object born,id:0x6c4e048L
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
gc: uncollectable 0000000006C4E048>
[]
gc: uncollectable 0000000006C48488>
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
gc: uncollectable 0000000006C48268>
4
3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
[<__main__.ClassA instance at 0x0000000006B1AF08>, <__main__.ClassA instance at 0x0000000006C4E048>, {'t': <__main__.ClassA instance at 0x0000000006C4E048>}, {'t': <__main__.ClassA instance at 0x0000000006B1AF08>}]
有三种情况会触发垃圾回收:
1.调用gc.collect(),
2.当gc模块的计数器达到阀值的时候。
3.程序退出的时候
gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)
3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)
每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):
Python垃圾回收机制–完美讲解!