詳解Python的垃圾回收機制方法

高洛峰
發布: 2017-03-24 17:34:45
原創
1315 人瀏覽過

一.垃圾回收機制
Python中的垃圾回收是以引用數為主,分代收集為輔。引用計數的缺陷是循環引用的問題。
在Python中,如果一個物件的參考數為0,Python虛擬機器就會回收這個物件的記憶體。

#encoding=utf-8
__author__ = 'kevinlu1010@qq.com'
 
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()會循環輸出這樣的結果,而且進程佔用的記憶體基本上不會變動

object born,id:0x237cf58
object del,id:0x237cf58
登入後複製


c1=ClassA()會建立一個對象,放在0x237cf58內存中,c1變數指向這個內存,這時候這個內存的引用計數是1
del c1後,c1變量不再指向0x237cf58內存,所以這塊內存的引用計數減一,等於0,所以就銷毀了這個對象,然後釋放記憶體。
導致引用計數+1的情況
物件被創建,例如a=23
物件被引用,例如b=a
物件被作為參數,傳入到一個函數中,例如func(a )
物件作為一個元素,儲存在容器中,例如list1=[a,a]
導致引用計數-1的情況
物件的別名被明確銷毀,例如del a
物件的別名被賦予新的對象,例如a=24
一個物件離開它的作用域,例如f函數執行完畢時,func函數中的局部變數(全域變數不會)
物件所在的容器被銷毀,或從容器中刪除物件
demo

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=1', sys.getrefcount(11) - 1
func(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
登入後複製


輸出:

init 24
after a=11 25
after b=1 26
in func function 28
after func(a) 26
after list1=[a,12,14] 27
after a=12 26
after del a 26
after del b 25
after del list1 24
登入後複製


問題:為什麼呼叫函數會令引用計數+2
檢視一個對象的參考計數
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
登入後複製


執行f2(),進程佔用的記憶體會不斷增加。

object born,id:0x237cf30
object born,id:0x237cf58
登入後複製


創建了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。
雖然它們兩個的物件都是可以被銷毀的,但是由於循環引用,導致垃圾回收器都不會回收它們,所以就會導致記憶體洩漏。
三.垃圾回收

deff3():
  # print gc.collect()
  c1=ClassA()
  c2=ClassA()
  c1.t=c2
  c2.t=c1
  del c1
  del c2
  print gc.garbage
  print gc.collect() #显式执行垃圾回收
  print gc.garbage
  time.sleep(10)
if __name__ == '__main__':
  gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
  f3()
登入後複製


輸出:
Python

gc: uncollectable <ClassA instance at 0230E918>
gc: uncollectable <ClassA instance at 0230E940>
gc: uncollectable <dict 0230B810>
gc: uncollectable <dict 02301ED0>
object born,id:0x230e918
object born,id:0x230e940
登入後複製



#垃圾回收後的物件會放在gc.garbage列表裡面
gc.collect()會傳回不可達的物件數目,4等於兩個物件以及它們對應的dict
有三種情況會觸發垃圾回收:
1.呼叫gc .collect(),
2.當gc模組的計數器達到閥值的時候。
3.程式退出的時候
四.gc模組常用功能解析

Garbage Collector interface
登入後複製


gc模組提供一個介面給開發者設定垃圾回收的選項。上面說到,採用引用計數的方法管理記憶體的一個缺陷是循環引用,而gc模組的一個主要功能就是解決循環引用的問題。
常用函數:
gc.set_debug(flags)
設定gc的debug日誌,一般設定為gc.DEBUG_LEAK
gc.collect([generation])
明確進行垃圾回收,可以輸入參數,0代表只檢查第一代的對象,1代表檢查一,二代的對象,2代表檢查一,二,三代的對象,如果不傳參數,執行一個full collection,也就是等於傳2 。
傳回不可達(unreachable objects)物件的數目
gc.set_threshold(threshold0[, threshold1[, threshold2])
設定自動執行垃圾回收的頻率。
gc.get_count()
取得目前自動執行垃圾回收的計數器,傳回長度為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):
当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
其他
如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,所以为了安全起见,gc模块会把对象放到gc.garbage中,但是不会销毁对象。
五.应用
 项目中避免循环引用
 引入gc模块,启动gc模块的自动清理循环引用的对象机制
 由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
 gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局

以上是詳解Python的垃圾回收機制方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!