1. 程式人生 > 實用技巧 >淺析Python垃圾回收機制!

淺析Python垃圾回收機制!

Python垃圾回收機制

目錄

1. 記憶體洩露

  • 程式本身沒有設計好,導致程式未能釋放已不再使用的記憶體
  • 程式碼在分配了某段記憶體後,因為設計錯誤,失去了對這段記憶體的控制,從而造成了記憶體的浪費

監控python程式記憶體佔用情況,psutil庫

import os 
import psutil

# 顯示當前程式佔用記憶體大小
def show_memory_info(hint):
    pid = os.getpid() # 獲取當前程序號
    p = psutil.Process(pid)
    info = p.memory_info()
    memory = info.rss / 1024. / 1024
    print(f"{hint} 記憶體用了:{memory}MB")

檢視物件佔用記憶體大小

import sys
a = [i for i in range(10000)]
memory = sys.getsizeof(a) / 1024
print(f"記憶體用了:{memory}KB")
# 記憶體用了:85.578125KB

2. Python什麼時候啟動垃圾回收機制?

2.1 計數引用

python中一切皆物件,所看到的一切變數,本質上都是物件的一個指標,當這個物件的引用次數為0的時候,說明這個物件永不可達,成為需要被回收的垃圾

# 檢視引用次數
import sys
a = []
print(sys.getrefcount(a)) # 兩次引用,一次a,一次getr
def func(a):
    # 四次引用,函式呼叫會產生兩次額外的引用,一次來自函式棧,另一個是函式引數
    print(sys.getrefcount(a))
func(a)
2
4
def func():
    show_memory_info('初始')
    a = [i for i in range(1000000)] # 列表生成式
    show_memory_info('建立之後')
    
func()
show_memory_info('結束之後') # 記憶體即可被釋放
初始 記憶體用了:52.4609375MB
建立之後 記憶體用了:91.921875MB
結束之後 記憶體用了:53.80859375MB
2.2 迴圈引用
  • python中使用標記清除演算法和分代收集,來啟動針對迴圈引用的自動垃圾回收
  • 標記清除演算法,圖論中的不可達概念
  • 分代收集演算法中每一代都有一個預設閾值,超過指定閾值之後就會啟動垃圾回收,如果垃圾回收啟動太頻繁,會造成程式效能低下,分代收集為了提高效能,因此不立刻回收。
def func():
    show_memory_info('初始')
    a = [i for i in range(1000000)] # 列表生成式
    b = [i for i in range(1000000)] # 列表生成式
    show_memory_info('建立之後')
    a.append(b)
    b.append(a)

func()
show_memory_info('結束之後') # 可以看到迴圈引用之後,記憶體依舊被佔用
初始 記憶體用了:77.125MB
建立之後 記憶體用了:163.8828125MB
結束之後 記憶體用了:163.8828125MB

顯示呼叫gc.collect()來啟動垃圾回收

import gc

def func():
    show_memory_info('初始')
    a = [i for i in range(1000000)] # 列表生成式
    b = [i for i in range(1000000)] # 列表生成式
    show_memory_info('建立之後')
    a.append(b)
    b.append(a)

func()
# 顯示呼叫gc.collect()來啟動垃圾回收
gc.collect()
show_memory_info('結束之後') 
初始 記憶體用了:77.609375MB
建立之後 記憶體用了:145.92578125MB
結束之後 記憶體用了:77.609375MB
問題:引用計數是0是啟動垃圾回收的充要條件嗎?

引用計數是其中最簡單的實現,不是充要條件,只能算作充分非必要條件,迴圈引用需要通過不可達判定,來確定是否可以回收。python中自動回收演算法包括標記清除演算法和分代收集。