天天看點

python垃圾回收機制淺解

在這裡插入代碼片

為什麼要有垃圾回收機制?

程式運作過程中會申請大量的記憶體空間,而對于一些無用的記憶體空間,如果不及時清理的話,會導緻記憶體使用完(記憶體溢出),導緻程式崩潰,是以,記憶體管理是一件重要且繁雜的事情,而python解釋器自帶的垃圾回收機制把程式員從繁雜的記憶體管理中解放出來。

引用計數

1).當一個對象的引用被建立或者複制時,對象的引用計數加1;當一個對象的引用被銷毀時,對象的引用計數減1.

2).當對象的引用計數減少為0時,就意味着對象已經再沒有被使用了,可以将其記憶體釋放掉。

引用計數就是:變量值被變量名關聯的次數

如:引用計數增加

  x=10(此時,變量值10的引用次數為1)

  y=x(此時,把x的記憶體位址給了y,此時,變量值10 的引用計數為2)

  引用計數減少

  x=3(此時,x和10解除關系,與3建立關系,變量值10的引用計數為1)

  del y(del是解除變量名y與變量值10之間的關系,變量值10的引用計數為0),變量值10的引用計數為0,其占用的記憶體空間就會被回收
           

循環引用

引用計數機制執行效率問題:變量值被關聯次數的增加或減少,都會引發引用計數機制的執行,這明顯存在效率問題,這就是引用計數的一個軟肋,但引用計數還存在一個緻命弱點,即循環引用(也稱交叉引用)。

# 變量名list1指向清單1,變量名list2指向清單2,如下
>>> list1=['清單1中的第一個元素']  # 清單1被引用一次   
>>> list2=['清單2中的第一個元素']  # 清單2被引用一次 
>>> list1.append(list2)             # 把清單2追加到list1中作為第二個元素,清單2的引用計數為2
>>> list2.append(list1)             # 把清單1追加到list2中作為第二個元素,清單1的引用計數為2
# list1與list2
del list1     #清單1的引用計數為1   
del list2     #清單2的引用計數為1
           

現在清單1和清單2都沒被其他變量名關聯,但引用計數不為0,是以不會被回收,這就是循環引用的危害,為解決這問題,python引進了‘标記-清除’,‘分代回收’。

标記-清除

1)它分為兩個階段:第一階段是标記階段,GC會把所有的活動對象打上标記,第二階段是把那些沒有标記的對象非活動對象進行回收。

2)對象之間通過引用(指針)連在一起,構成一個有向圖

3)從根對象(root object)出發,沿着有向邊周遊對象,可達的(reachable)對象标記為活動對象,不可達的對象就是要被清除的非活動對象,根對象就是全局變量、調用棧、寄存器。

注:像是PyIntObject、PyStringObject這些不可變對象是不可能産生循環引用的,因為它們内部不可能持有其它對象的引用。

容器對象(list,set,dict,class,instance)都可以包含其他對象的引用,是以都可能産生循環引用。在了解 ‘标記-清除’ 之前,先得知道一個知識點:記憶體中有兩塊區域:堆區 與 棧區,在定義變量時,變量名放在棧區,變量值放在堆區,記憶體管理是對堆區的管理。

python垃圾回收機制淺解

當有效記憶體空間被耗盡的時候,就會停止整個程式,然後進行兩項工作,第一是标記,第二是清除

标記:周遊所有的GC Roots對象(棧區中的所有内容或者線程都可以作為GC Roots對象),然後将所有GC Roots對象可以直接通路或間接通路的對象标記為存活對象。

清除:周遊堆區中所有的對象,将沒有标記的對象全部清除

分代回收

1. 分代回收是建立在标記清除技術基礎之上的,是一種以空間換時間的操作方式。

2. Python将記憶體分為了3“代”,分别為年輕代(第0代)、中年代(第1代)、老年代(第2代)

3. 他們對應的是3個連結清單,它們的垃圾收集頻率與對象的存活時間的增大而減小。

4. 新建立的對象都會配置設定在年輕代,年輕代連結清單的總數達到上限時,Python垃圾收集機制就會被觸發

5. 把那些可以被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推

6. 老年代中的對象是存活時間最久的對象,甚至是存活于整個系統的生命周期内。