面試題-python 垃圾回收機制?


前言

簡歷上寫着熟悉 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM