【轉】python裏面的“垃圾”是怎麼回收的

大家都熟悉C和Java的垃圾回收機制,可以幫助機器很好的去釋放內存空間,提升內存的使用效率。那麼對於膠水語言Python來說,是怎麼樣做到垃圾回收的呢?

前言:

對於python來說,一切皆爲對象,所有的變量賦值都遵循着對象引用機制。程序在運行的時候,需要在內存中開闢出一塊空間,用於存放運行時產生的臨時變量;計算完成後,再將結果輸出到永久性存儲器中。如果數據量過大,內存空間管理不善就很容易出現OOM(out of memory)的情況,俗稱爆內存,程序可能被操作系統終止。

而對於服務器,內存管理則顯得更爲重要,不然很容易引發內存泄露。

這裏說的泄露,並不是說我們的內存出了信息安全問題,被惡意程序利用了,而是指程序本身沒有設計好,導致程序未能釋放已經不再使用的內存。

 

計數引用

因爲python裏面一切皆對象,所有我們看到的一切變量,實質上是對象的一個指針。

當一個對象不再調用的時候,也就是當這個對象的引用計數(指針數)爲0的時候,說明這個對象永不可達,自然也就成爲了垃圾,需要被回收。可以簡單的理解爲沒有任何變量再指向它。

import os
import psutil

#顯示當前python程序佔用的內存大小
def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)

    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used:{}MB'.format(hint,memory))

def func():
    show_memory_info('initial')
    a = [i for i in range(10000000)]
    show_memory_info('after a created')
func()
show_memory_info('finished')

########運行結果#############
initial memory used:4.94140625MB
after a created memory used:197.484375MB
finished memory used:5.2421875MB

從輸出結果,我們可以看到在調用函數func()之後,在列表a被創建之後,內存迅速佔用到了197M;而在函數運行結束之後,內存則返回正常。這是因爲,函數內部聲明的列表a是局部變量,在函數返回後,局部變量的引用會被註銷掉;此時,列表a所指代對象的引用數爲0,python便會執行垃圾回收,因此之前佔用的大量內存又回來了。

 

接下來,讓我們再來看另外一段代碼:

import os
import psutil

def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)
    
    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used:{}MB'.format(hint,memory))

def func():
    show_memory_info('inital')
    global a
    a = [i for i in range(10000000)]
    show_memory_info('after a created!')

func()
show_memory_info('finished!')

################輸出結果################
inital memory used:4.8671875MB
after a created! memory used:197.4375MB
finished! memory used:197.4375MB

從這段代碼中,我們可以看到 global a表示將a聲明爲全局變量。那麼,即使函數返回後,列表的引用依然存在,對象並沒有因爲函數調用結束而被回收掉,依然佔用着分配給它的大量內存。那麼,我們再來往下思考,如果我們把變量返回到主程序中接受,結果會怎麼樣呢? 再來看一段代碼:

def func():
    show_memory_info('initail')
    a = [i for i in range(10000000)]
    show_memory_info('after a created:')
    return a 

a = func()
show_memory_info('finished!')

###################output#################
inital memory used:4.8671875MB
after a created! memory used:197.41015625MB
finished! memory used:197.41015625MB

從輸出結果可以看出,但我們選擇把局部變量的值引用到主程序的時候,在程序執行結束之後,內存的使用情況和作爲全局變量完全一致。說明,這種方法依然會造成內存得不到釋放。

那麼我們怎麼看到變量被引用了多少次呢?讓我們來看下面一段代碼

import sys

a = []
print(sys.getrefcount(a))

def func(a):
    print(sys.getrefcount(a))

func(a)
print(sys.getrefcount(a))

#############output#############
2
4
2

其中涉及到函數調用的時候,會額爲增加兩次

1.函數棧

2.函數調用

從這裏可以看到python並不需要我們像C語言那樣的釋放內存,但是python同樣給我們提供了手動釋放內錯的方法:gc.collect()

看下面一段代碼:

import sys
import gc
import os
import psutil

def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)

    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used:{}MB'.format(hint,memory))

show_memory_info('initial')
a = [i for i in range(10000000)]

del a
gc.collect()
show_memory_info('finished')
print(a)

###############output ####################
initial memory used:4.94140625MB
finished memory used:4.98828125MB
Traceback (most recent call last):
  File "varible.py", line 20, in <module>
    print(a)
NameError: name 'a' is not defined

截止目前,貌似python的垃圾回收機制非常的簡單,只要對象引用次數爲0,必定爲觸發gc,那麼引用次數爲0是否是觸發gc的充要條件呢?

原文鏈接:https://www.heroyf.club/2019/10/24/python_gc/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章