前言
簡歷上寫着熟悉 python 面試官上來就問:說下python 垃圾回收機制?一盆冷水潑過來,瞬間感覺 python 不香了。
Python中,主要通過引用計數(Reference Counting)進行垃圾回收。
引用計數
在Python中每一個對象的核心就是一個結構體PyObject,它的內部有一個引用計數器(ob_refcnt)。
程序在運行的過程中會實時的更新 ob_refcnt 的值,來反映引用當前對象的名稱數量。
當對象被創建時, 就創建了一個引用計數, 當這個對象不再需要時, 也就是說, 這個對象的引用計數變為0 時, 它被垃圾回收。
但是回收不是"立即"的, 由解釋器在適當的時機,將垃圾對象占用的內存空間回收。
sys.getrefcount() 可以查看對象的引用次數,先自己先有個class 創建一個對象,此時引用次數是1,由於 sys.getrefcount() 也會引用一次,所以看到的會在引用次數基礎上+1
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import sys
class MyObject():
def __init__(self):
self.x = 1
a = MyObject() # 創建一個對象
print("MyObject 引用次數:", sys.getrefcount(a)) # 查看引用次數
運行結果:MyObject 引用次數: 2
導致引用計數 +1 的情況
- 對象被創建,例如 a=23
- 對象被引用,例如 b=a
- 對象被作為參數,傳入到一個函數中,例如func(a)
- 對象作為一個元素,存儲在容器中,例如list1=[a,a]
導致引用計數-1 的情況
- 對象的別名被顯式銷毀,例如del a
- 對象的別名被賦予新的對象,例如a=24
- 一個對象離開它的作用域,例如 f 函數執行完畢時,func函數中的局部變量(全局變量不會)
- 對象所在的容器被銷毀,或從容器中刪除對象
對象銷毀
下面代碼a增加一次引用,賦值給a后,b和a都是指向同一個對象,當我們不用的時候就可以用del 銷毀對象a和b
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import sys
class MyObject():
def __init__(self):
self.x = 1
a = MyObject() # 創建一個對象
print("MyObject 引用次數:", sys.getrefcount(a)) # 查看引用次數
# a增加一次引用
b = a
print("MyObject 引用次數:", sys.getrefcount(a)) # 查看引用次數
# 銷毀對象b
del b
print("MyObject 引用次數:", sys.getrefcount(a)) # 查看引用次數
# 銷毀對象a
del a
對象作為參數,傳到函數里面也會被引用一次,看下面這個案例
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2次
b = a
print(sys.getrefcount(a)) # 3次
c = b
d = b
e = c
f = e
g = d
print(sys.getrefcount(a)) # 8次
輸出結果
2
3
8
a、b、c、d、e、f、g 這些變量全部指代的是同一個對象,而 sys.getrefcount() 函數並不是統計一個指針,而是要統計一個對象被引用的次數,所以最后一共會有 8 次引用。
如果我們一個個去銷毀對象,很顯然會浪費時間,於是可以用gc來垃圾回收了,gc.collect() 即可手動啟動垃圾回收
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import sys
import gc
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2次
b = a
print(sys.getrefcount(a)) # 3次
c = b
d = b
e = c
f = e
g = d
print(sys.getrefcount(a)) # 8次
del a
gc.collect() # 垃圾回收
循環引用
當a對象引用b,b對象也引用a,兩個互相引用的時候,互相引用導致它們的引用數都不為 0。
初始化的時候,會生成一個大的列表[i for i in range(100000)],導致占用很大的內存
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import sys
class MyObject():
def __init__(self):
self.y = [i for i in range(1000000)]
self.x = 1
while True:
a = MyObject() # 創建一個對象
b = MyObject()
print("MyObject 引用次數a:", sys.getrefcount(a)) # 查看引用次數
print("MyObject 引用次數b:", sys.getrefcount(b)) # 查看引用次數
a.x = b # a的x屬性賦值b
b.x = a # b的x屬性賦值a
# 銷毀對象a和b
del a
del b
運行一段時間后,可以觀察內存的變化,會一直增加不釋放
使用 gc.collect() 垃圾回收
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import sys
import gc
class MyObject():
def __init__(self):
self.y = [i for i in range(1000000)]
self.x = 1
while True:
a = MyObject() # 創建一個對象
b = MyObject()
print("MyObject 引用次數a:", sys.getrefcount(a)) # 查看引用次數
print("MyObject 引用次數b:", sys.getrefcount(b)) # 查看引用次數
a.x = b # a的x屬性賦值b
b.x = a # b的x屬性賦值a
# 銷毀對象a和b
del a
del b
gc.collect()
再次運行,內存就得到釋放了
在Python中,主要通過引用計數進行垃圾回收;通過 “標記-清除” 解決容器對象可能產生的循環引用問題;通過 “分代回收” 以空間換時間的方法提高垃圾回收效率。
參考資料http://c.biancheng.net/view/5540.html
參考資料https://www.cnblogs.com/donghe123/p/13275183.html
參考資料https://testerhome.com/topics/16556