垃圾回收
首先介紹兩個畫圖的工具:objgraph 包和在線繪圖網站 draw.io。具體的使用以后再寫。
1.引用計數
Python 中,每個對象都有存有指向該對象的引用總數,即:引用計數(reference count);
可以使用 sys 包中的 getrefcount(),來查看某個對象的引用計數;
需要注意的是,當使用某個引用作為參數,傳遞給 getrefcount() 時,參數實際上創建了一個臨時的引用。因此,getrefcount() 所得到的結果,會比期望的多 1 ;
from sys import getrefcount
a = [1, 2, 3]
print(getrefcount(a)) # 2
b = a
print(getrefcount(b)) # 3
Python 的一個容器對象(container),比如列表、字典等,可以包含多個對象。實際上,容器對象中包含的並不是元素對象本身,是指向各個元素對象的引用;
即使是 a = 1 這一賦值方式,實際上是讓字典的一個鍵值 "a" 的元素引用整數對象 1。該字典對象用於記錄所有的全局引用。該詞典引用了整數對象 1。我們可以通過內置函數 globals() 來查看該字典。
容器對象的引用可能構成很復雜的拓撲結構。我們可以用 objgraph 包來繪制其引用關系,比如:
import objgraph
x = [1, 2, 3]
y = [x, dict(key1=x)]
z = [y, (x, y)]
objgraph.show_refs([z])
兩個對象可能相互引用,從而構成所謂的引用環(reference cycle):
a = []
b = [a]
a.append(b)
objgraph.show_refs([a])
即使是一個對象,只需要自己引用自己,也能構成引用環:
c = []
c.append(c)
print(getrefcount(c))
objgraph.show_refs([c])


2.孤立的引用環--標記清除法
引用環的存在會給垃圾回收機制帶來很大的困難,可能構成無法使用,但引用計數不為 0 的一些對象:
"""
下面創建了兩個列表對象,並引用對方,構成一個引用環;
刪除了a,b引用之后,這兩個對象不可能再從程序中調用,就沒有什么用處了;
但是由於引用環的存在,這兩個對象的引用計數都沒有降到0,不會被垃圾回收;
"""
a = []
b = [a]
a.append(b)
del a
del b
為了回收這樣的引用環,Python 會復制每個對象的引用計數,可以記為 gc_ref。假設,每個對象 i 的引用計數為 gc_ref_i。Python會遍歷所有的對象 i。對於每個對象 i 引用的對象 j,將相應的 gc_ref_j - 1;
在結束遍歷后,gc_ref 不為 0 的對象,和這些對象引用的對象,以及繼續更下游引用的對象,需要被保留,而其它的對象則被垃圾回收;稱之為“標記清除法”.
3.引用計數為 0 時
當 Python 中的對象越來越多,它們將占據越來越大的內存,並在適當的時候啟動垃圾回收(garbage collection),將沒用的對象清除。Python 某個對象的引用計數降為 0 時,說明沒有任何引用指向該對象,該對象就要被回收了;
然而,垃圾回收時,Python 不能進行其它的任務,頻繁的垃圾回收將大大降低 Python 的工作效率。如果內存中的對象不多,就沒有必要總啟動垃圾回收。所以,Python 只會在特定條件下,自動啟動垃圾回收:
當 Python 運行時,會記錄其中分配對象(object allocation)和取消分配對象(object deallocation)的次數。當兩者的差值高於某個閾值時,垃圾回收才會啟動。可以通過 gc 模塊的 get_threshold() 方法,查看該閾值:
import gc
gc.get_threshold() # (700, 10, 10),兩個10是與分代回收相關的閾值,700 是垃圾回收啟動閾值;
gc.set_threshold(800, 10, 5) # 重新設置垃圾回收的相關閾值
gc.collect() # 手動啟動垃圾回收, gc.collect()
4.分代回收
Python 同時采用了分代(generation)回收的策略。這一策略的基本假設是:
存活時間越久的對象,越不可能在后面的程序中變成垃圾。我們的程序往往會產生大量的對象,許多對象很快產生和消失,但也有一些對象長期被使用。出於信任和效率,對於這樣一些“長壽”對象,我們相信它們的用處,所以減少在垃圾回收中掃描它們的頻率。
Python 將所有的對象分為 0,1,2 三代。所有的新建對象都是 0 代對象。當某一代對象經歷過垃圾回收,依然存活,那么它就被歸入下一代對象。垃圾回收啟動時,一定會掃描所有的 0 代對象。如果 0 代經過一定次數垃圾回收,那么就啟動對 0 代和 1代 的掃描清理。當 1 代也經歷了一定次數的垃圾回收后,那么會啟動對 0,1,2,即對所有對象進行掃描。
(700, 10, 10)中的兩個 10 代表:每 10 次 0 代垃圾回收,會有 1 次 1 代的垃圾回收;每 10 次 1 代的垃圾回收,會有 1 次的 2 代垃圾回收;
5.dot 解析網站
objgraph.show_refs() 生成的 dot 文件解析網站 https://onlineconvertfree.com/zh/
import objgraph
a = [1,2,3]
b = [4,5,6]
a.append(b)
b.append(a)
objgraph.show_refs(a)
objgraph.show_refs([a])
objgraph.show_refs([b])


