python实现链表的删除_Python垃圾回收机制
python作為一門解釋型語言,以代碼簡潔易懂著稱。我們可以直接對名稱賦值,而不必聲明類型。名稱類型的確定、內存空間的分配與釋放都是由python解釋器在運行時進行的。python這一自動管理內存功能極大的減小了程序員負擔,這也是成就python自身的重要原因之一。以下簡要介紹一下Python的三種垃圾回收機制:
引用計數(shù)
Python中,主要通過引用計數(shù)(Reference Counting)進行垃圾回收。
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
在Python中每一個對象的核心就是一個結構體PyObject,它的內部有一個引用計數(shù)器(ob_refcnt)。程序在運行的過程中會實時的更新ob_refcnt的值,來反映引用當前對象的名稱數(shù)量。當某對象的引用計數(shù)值為0,那么它的內存就會被立即釋放掉。
以下情況是導致引用計數(shù)加一的情況:
對象被創(chuàng)建,例如a=2
對象被引用,b=a
對象被作為參數(shù),傳入到一個函數(shù)中
對象作為一個元素,存儲在容器中
下面的情況則會導致引用計數(shù)減一:
對象別名被顯示銷毀 del
對象別名被賦予新的對象
一個對象離開他的作用域
對象所在的容器被銷毀或者是從容器中刪除對象
我們還可以通過sys包中的getrefcount()來獲取一個名稱所引用的對象當前的引用計數(shù)(注意,這里getrefcount()本身會使得引用計數(shù)加一)
sys.getrefcount(a)
引用計數(shù)法有其明顯的優(yōu)點,如高效、實現(xiàn)邏輯簡單、具備實時性,一旦一個對象的引用計數(shù)歸零,內存就直接釋放了。不用像其他機制等到特定時機。將垃圾回收隨機分配到運行的階段,處理回收內存的時間分攤到了平時,正常程序的運行比較平穩(wěn)。但是,引用計數(shù)也存在著一些缺點,通常的缺點有:
邏輯簡單,但實現(xiàn)有些麻煩。每個對象需要分配單獨的空間來統(tǒng)計引用計數(shù),這無形中加大的空間的負擔,并且需要對引用計數(shù)進行維護,在維護的時候很容易會出錯。
在一些場景下,可能會比較慢。正常來說垃圾回收會比較平穩(wěn)運行,但是當需要釋放一個大的對象時,比如字典,需要對引用的所有對象循環(huán)嵌套調用,從而可能會花費比較長的時間。
循環(huán)引用。這將是引用計數(shù)的致命傷,引用計數(shù)對此是無解的,因此必須要使用其它的垃圾回收算法對其進行補充。
也就是說,Python 的垃圾回收機制,很大一部分是為了處理可能產(chǎn)生的循環(huán)引用,是對引用計數(shù)的補充。
標記清除(解決循環(huán)引用)
Python采用了“標記-清除”(Mark and Sweep)算法,解決容器對象可能產(chǎn)生的循環(huán)引用問題。(注意,只有容器對象才會產(chǎn)生循環(huán)引用的情況,比如列表、字典、用戶自定義類的對象、元組等。而像數(shù)字,字符串這類簡單類型不會出現(xiàn)循環(huán)引用。作為一種優(yōu)化策略,對于只包含簡單類型的元組也不在標記清除算法的考慮之列)
跟其名稱一樣,該算法在進行垃圾回收時分成了兩步,分別是:
A)標記階段,遍歷所有的對象,如果是可達的(reachable),也就是還有對象引用它,那么就標記該對象為可達;
B)清除階段,再次遍歷對象,如果發(fā)現(xiàn)某個對象沒有標記為可達,則就將其回收。
如下圖所示,在標記清除算法中,為了追蹤容器對象,需要每個容器對象維護兩個額外的指針,用來將容器對象組成一個雙端鏈表,指針分別指向前后兩個容器對象,方便插入和刪除操作。python解釋器(Cpython)維護了兩個這樣的雙端鏈表,一個鏈表存放著需要被掃描的容器對象,另一個鏈表存放著臨時不可達對象。在圖中,這兩個鏈表分別被命名為”O(jiān)bject to Scan”和”Unreachable”。圖中例子是這么一個情況:link1,link2,link3組成了一個引用環(huán),同時link1還被一個變量A(其實這里稱為名稱A更好)引用。link4自引用,也構成了一個引用環(huán)。從圖中我們還可以看到,每一個節(jié)點除了有一個記錄當前引用計數(shù)的變量ref_count還有一個gc_ref變量,這個gc_ref是ref_count的一個副本,所以初始值為ref_count的大小。
gc啟動的時候,會逐個遍歷”O(jiān)bject to Scan”鏈表中的容器對象,并且將當前對象所引用的所有對象的gc_ref減一。(掃描到link1的時候,由于link1引用了link2,所以會將link2的gc_ref減一,接著掃描link2,由于link2引用了link3,所以會將link3的gc_ref減一…..)像這樣將”O(jiān)bjects to Scan”鏈表中的所有對象考察一遍之后,兩個鏈表中的對象的ref_count和gc_ref的情況如下圖所示。這一步操作就相當于解除了循環(huán)引用對引用計數(shù)的影響。
接著,gc會再次掃描所有的容器對象,如果對象的gc_ref值為0,那么這個對象就被標記為GC_TENTATIVELY_UNREACHABLE,并且被移至”Unreachable”鏈表中。下圖中的link3和link4就是這樣一種情況。
如果對象的gc_ref不為0,那么這個對象就會被標記為GC_REACHABLE。同時當gc發(fā)現(xiàn)有一個節(jié)點是可達的,那么他會遞歸式的將從該節(jié)點出發(fā)可以到達的所有節(jié)點標記為GC_REACHABLE,這就是下圖中l(wèi)ink2和link3所碰到的情形。
除了將所有可達節(jié)點標記為GC_REACHABLE之外,如果該節(jié)點當前在”Unreachable”鏈表中的話,還需要將其移回到”O(jiān)bject to Scan”鏈表中,下圖就是link3移回之后的情形。
第二次遍歷的所有對象都遍歷完成之后,存在于”Unreachable”鏈表中的對象就是真正需要被釋放的對象。如上圖所示,此時link4存在于Unreachable鏈表中,gc隨即釋放之。
上面描述的垃圾回收的階段,會暫停整個應用程序,等待標記清除結束后才會恢復應用程序的運行。
分代回收
在循環(huán)引用對象的回收中,整個應用程序會被暫停,為了減少應用程序暫停的時間,Python 通過“分代回收”(Generational Collection)以空間換時間的方法提高垃圾回收效率。
分代回收是基于這樣的一個統(tǒng)計事實,對于程序,存在一定比例的內存塊的生存周期比較短;而剩下的內存塊,生存周期會比較長,甚至會從程序開始一直持續(xù)到程序結束。生存期較短對象的比例通常在 80%~90% 之間,這種思想簡單點說就是:對象存在時間越長,越可能不是垃圾,應該越少去收集。這樣在執(zhí)行標記-清除算法時可以有效減小遍歷的對象數(shù),從而提高垃圾回收的速度。
python gc給對象定義了三種世代(0,1,2),每一個新生對象在generation zero中,如果它在一輪gc掃描中活了下來,那么它將被移至generation one,在那里他將較少的被掃描,如果它又活過了一輪gc,它又將被移至generation two,在那里它被掃描的次數(shù)將會更少。
gc的掃描在什么時候會被觸發(fā)呢?答案是當某一世代中被分配的對象與被釋放的對象之差達到某一閾值的時候,就會觸發(fā)gc對某一世代的掃描。值得注意的是當某一世代的掃描被觸發(fā)的時候,比該世代年輕的世代也會被掃描。也就是說如果世代2的gc掃描被觸發(fā)了,那么世代0,世代1也將被掃描,如果世代1的gc掃描被觸發(fā),世代0也會被掃描。
該閾值可以通過下面兩個函數(shù)查看和調整:
gc.get_threshold()# (threshold0, threshold1, threshold2).
gc.set_threshold(threshold0[, threshold1[, threshold2]])
下面對set_threshold()中的三個參數(shù)threshold0, threshold1, threshold2進行介紹。gc會記錄自從上次收集以來新分配的對象數(shù)量與釋放的對象數(shù)量,當兩者之差超過threshold0的值時,gc的掃描就會啟動,初始的時候只有世代0被檢查。如果自從世代1最近一次被檢查以來,世代0被檢查超過threshold1次,那么對世代1的檢查將被觸發(fā)。相同的,如果自從世代2最近一次被檢查以來,世代1被檢查超過threshold2次,那么對世代2的檢查將被觸發(fā)。get_threshold()是獲取三者的值,默認值為(700,10,10).
總結
總體來說:
總結
以上是生活随笔為你收集整理的python实现链表的删除_Python垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot读取JSON文件
- 下一篇: python元组和列表的联系_Pytho