溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python是怎么實(shí)現(xiàn)內(nèi)存管理的

發(fā)布時(shí)間:2022-10-13 09:49:43 來源:億速云 閱讀:150 作者:iii 欄目:編程語言

今天小編給大家分享一下Python是怎么實(shí)現(xiàn)內(nèi)存管理的的相關(guān)知識點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

Python提供了自動化的內(nèi)存管理,也就是說內(nèi)存空間的分配與釋放都是由Python解釋器在運(yùn)行時(shí)自動進(jìn)行的,自動管理內(nèi)存功能極大的減輕程序員的工作負(fù)擔(dān),也能夠幫助程序員在一定程度上解決內(nèi)存泄露的問題。以CPython解釋器為例,它的內(nèi)存管理有三個(gè)關(guān)鍵點(diǎn):引用計(jì)數(shù)、標(biāo)記清理、分代收集。

引用計(jì)數(shù):對于CPython解釋器來說,Python中的每一個(gè)對象其實(shí)就是PyObject結(jié)構(gòu)體,它的內(nèi)部有一個(gè)名為ob_refcnt 的引用計(jì)數(shù)器成員變量。程序在運(yùn)行的過程中ob_refcnt的值會被更新并藉此來反映引用有多少個(gè)變量引用到該對象。當(dāng)對象的引用計(jì)數(shù)值為0時(shí),它的內(nèi)存就會被釋放掉。

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

以下情況會導(dǎo)致引用計(jì)數(shù)加1

  • 對象被創(chuàng)建

  • 對象被引用

  • 對象作為參數(shù)傳入到一個(gè)函數(shù)中

  • 對象作為元素存儲到一個(gè)容器中

以下情況會導(dǎo)致引用計(jì)數(shù)減1

  • del語句顯示刪除對象引用

  • 對象引用被重新賦值其他對象

  • 一個(gè)對象離開它所在的作用域

  • 持有該對象的容器自身被銷毀

  • 持有該對象的容器刪除該對象

可以通過sys模塊的getrefcount函數(shù)來獲得對象的引用計(jì)數(shù)。引用計(jì)數(shù)的內(nèi)存管理方式在遇到循環(huán)引用的時(shí)候就會出現(xiàn)致命傷,因此需要其他的垃圾回收算法對其進(jìn)行補(bǔ)充。

標(biāo)記清理:CPython使用了“標(biāo)記-清理”(Mark and Sweep)算法解決容器類型可能產(chǎn)生的循環(huán)引用問題。該算法在垃圾回收時(shí)分為兩個(gè)階段:標(biāo)記階段,遍歷所有的對象,如果對象是可達(dá)的(被其他對象引用),那么就標(biāo)記該對象為可達(dá);清除階段,再次遍歷對象,如果發(fā)現(xiàn)某個(gè)對象沒有標(biāo)記為可達(dá),則就將其回收。CPython底層維護(hù)了兩個(gè)雙端鏈表,一個(gè)鏈表存放著需要被掃描的容器對象(姑且稱之為鏈表A),另一個(gè)鏈表存放著臨時(shí)不可達(dá)對象(姑且稱之為鏈表B)。為了實(shí)現(xiàn)“標(biāo)記-清理”算法,鏈表中的每個(gè)節(jié)點(diǎn)除了有記錄當(dāng)前引用計(jì)數(shù)的ref_count變量外,還有一個(gè)gc_ref變量,這個(gè)gc_refref_count的一個(gè)副本,所以初始值為ref_count的大小。執(zhí)行垃圾回收時(shí),首先遍歷鏈表A中的節(jié)點(diǎn),并且將當(dāng)前對象所引用的所有對象的gc_ref1,這一步主要作用是解除循環(huán)引用對引用計(jì)數(shù)的影響。再次遍歷鏈表A中的節(jié)點(diǎn),如果節(jié)點(diǎn)的gc_ref值為0,那么這個(gè)對象就被標(biāo)記為“暫時(shí)不可達(dá)”(GC_TENTATIVELY_UNREACHABLE)并被移動到鏈表B中;如果節(jié)點(diǎn)的gc_ref不為0,那么這個(gè)對象就會被標(biāo)記為“可達(dá)“(GC_REACHABLE),對于”可達(dá)“對象,還要遞歸的將該節(jié)點(diǎn)可以到達(dá)的節(jié)點(diǎn)標(biāo)記為”可達(dá)“;鏈表B中被標(biāo)記為”可達(dá)“的節(jié)點(diǎn)要重新放回到鏈表A中。在兩次遍歷之后,鏈表B中的節(jié)點(diǎn)就是需要釋放內(nèi)存的節(jié)點(diǎn)。

分代回收:在循環(huán)引用對象的回收中,整個(gè)應(yīng)用程序會被暫停,為了減少應(yīng)用程序暫停的時(shí)間,Python 通過分代回收(空間換時(shí)間)的方法提高垃圾回收效率。分代回收的基本思想是:對象存在的時(shí)間越長,是垃圾的可能性就越小,應(yīng)該盡量不對這樣的對象進(jìn)行垃圾回收。CPython將對象分為三種世代分別記為0、1、2,每一個(gè)新生對象都在第0代中,如果該對象在一輪垃圾回收掃描中存活下來,那么它將被移到第1代中,存在于第1代的對象將較少的被垃圾回收掃描到;如果在對第1代進(jìn)行垃圾回收掃描時(shí),這個(gè)對象又存活下來,那么它將被移至第2代中,在那里它被垃圾回收掃描的次數(shù)將會更少。分代回收掃描的門限值可以通過gc模塊的get_threshold函數(shù)來獲得,該函數(shù)返回一個(gè)三元組,分別表示多少次內(nèi)存分配操作后會執(zhí)行0代垃圾回收,多少次0代垃圾回收后會執(zhí)行1代垃圾回收,多少次1代垃圾回收后會執(zhí)行2代垃圾回收。需要說明的是,如果執(zhí)行一次2代垃圾回收,那么比它年輕的代都要執(zhí)行垃圾回收。如果想修改這幾個(gè)門限值,可以通過gc模塊的set_threshold函數(shù)來做到。

以上就是“Python是怎么實(shí)現(xiàn)內(nèi)存管理的”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI