1. 程式人生 > >Python中的垃圾回收機制

Python中的垃圾回收機制

disable 帶來 res 相互 obj 全局變量 模塊 函數 計數器

  當我們聲明一個對象的時候,例如str="abcdef",當我們不再使用str這個對象的時候,這個對象就是一個臟對象,垃圾對象,但是它還在占著內存,畢竟我們的電腦內存有限,所以應該有一個機制來回收它以及類似的對象。現在的高級語言如java,c#等,都采用了垃圾收集機制,而不再是c,c++裏用戶自己管理維護內存的方式。自己管理內存極其自由,可以任意申請內存,但如同一把雙刃劍,為大量內存泄露,懸空指針等bug埋下隱患。 對於一個字符串、列表、類甚至數值都是對象,且定位簡單易用的語言,自然不會讓用戶去處理如何分配回收內存的問題。 python裏也同java一樣采用了垃圾收集機制,不過不一樣的是: python采用的是引用計數機制為主,分代收集機制為輔的策略。

一、引用計數機這是Python垃圾回收使用的重要機制。

  python裏每一個東西都是對象,它們的核心就是一個結構體:PyObject

typedef struct_object {
    int ob_refcnt;
    struct_typeobject *ob_type;
} PyObject;

  PyObject是每個對象必有的內容,其中ob_refcnt就是做為引用計數。當一個對象有新的引用時,它的ob_refcnt就會增加,當引用它的對象被刪除,它的ob_refcnt就會減少。當引用計數為0時,該對象生命就結束了。

引用計數機制的優點:
  • 簡單
  • 實時性:一旦沒有引用,內存就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時。
引用計數機制的缺點:
  • 維護引用計數消耗資源
  • 循環引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

  list1與list2相互引用,如果不存在其他對象對它們的引用,list1與list2的引用計數也仍然為1,所占用的內存永遠無法被回收,這將是致命的。 對於如今的強大硬件,缺點1尚可接受,但是循環引用導致內存泄露,註定python還將引入新的回收機制。(分代收集)

1、導致引用計數+1的情況

  • 對象被創建,例如a=23
  • 對象被引用,例如b=a
  • 對象被作為參數,傳入到一個函數中,例如func(a)
  • 對象作為一個元素,存儲在容器中,例如list1=[a,a]

2、導致引用計數-1的情況

  • 對象的別名被顯式銷毀,例如del a
  • 對象的別名被賦予新的對象,例如a=24
  • 一個對象離開它的作用域,例如f函數執行完畢時,func函數中的局部變量(全局變量不會)
  • 對象所在的容器被銷毀,或從容器中刪除對象

3、查看一個對象的引用計數

import sys
a = "hello"
sys.getrefcount(a) #2

  可以查看a對象的引用計數,但是比正常計數大1,因為調用函數的時候傳入a,這會讓a的引用計數+1。

4、循環引用導致內存泄露

import gc


class ClassA():
    def __init__(self):
        print(生成對象,id:%s % str(id(self)))


def f2():
    while True:
        c1 = ClassA()
        c2 = ClassA()
        c1.t = c2
        c2.t = c1
        del c1
        del c2


# python默認是開啟垃圾回收的,可以通過下面代碼來將其關閉
gc.disable()

f2()

  執行f2(),進程占用的內存會不斷增大。

  • 創建了c1,c2後這兩塊內存的引用計數都是1,執行c1.t=c2c2.t=c1後,這兩塊內存的引用計數變成2.
  • 在del c1後,引用計數變為1,由於不是為0,所以c1對象不會被銷毀;同理,c2對象的引用數也是1。
  • python默認是開啟垃圾回收功能的,但是由於以上程序已經將其關閉,因此導致垃圾回收器都不會回收它們,所以就會導致內存泄露。

有三種情況會觸發垃圾回收:

  1. 當gc模塊的計數器達到閥值的時候,自動回收垃圾
  2. 調用gc.collect(),手動回收垃圾
  3. 程序退出的時候,python解釋器來回收垃圾

二、分代收集機制:

  在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)

Python中的垃圾回收機制