Python記憶體回收機制,python記憶體回收

來源:互聯網
上載者:User

Python記憶體回收機制,python記憶體回收

對於Python記憶體回收機制主要有三個,首先是使用引用計數來跟蹤和回收垃圾,為瞭解決迴圈
引用問題,就採用標記-清除的方法,標記-清除的方法所帶來的額外操作實際上與系統中總的記憶體
塊的總數是相關的,當需要回收的記憶體塊越多,垃圾檢查帶來的額外操作就越多,為了提高垃圾收集
的效率,採用“空間換時間的策略”,即使用分代機制,對於長時間沒有被回收的記憶體就減少對它的
記憶體回收效率。

首先看一下Python的記憶體管理架構:

layer 3: Object-specific memory(int/dict/list/string....)Python 實現並維護更高抽象層次的記憶體管理原則, 主要是各類特定對象的緩衝池機制layer 2: Python's object allocatorPython 實現並維護實現了建立/銷毀Python對象的介面(PyObject_New/Del), 涉及對象參數/引用計數等layer 1: Python's raw memory allocator (PyMem_ API)Python 實現並維護, 封裝了第0層的記憶體管理介面, 提供統一的raw memory管理介面封裝的原因: 不同作業系統 C 行為不一定一致, 保證可移植性, 相同語義相同行為layer 0: Underlying general-purpose allocator (ex: C library malloc)作業系統提供的記憶體管理介面, 由作業系統實現並管理, Python不能干涉這一層的行為
引用計數機制

引用計數是一種垃圾收集機制,而且也是一種最直觀,最簡單的記憶體回收技術 當一個對象的引用被建立或者複製時,對象的引用計數加1;當一個對象的引用被銷毀 對象的引用計數減1。如果對象的引用計數減少為0,那麼就意味著對象已經不會被任何人使用,可以將其
所佔有的記憶體釋放。
引用計數機制的優點:即時性,對於任何記憶體一旦沒有指向它的引用,就會立即被回收(這裡需要滿足閾值才可以)
引用計數機制的缺點:引用計數機制所帶來的維護引用計數的額外操作與Python運行中所啟動並執行記憶體配置和釋放,引用賦值的
次數是成正比的,為了與引用計數機制搭配,在記憶體的分配和釋放上獲得最高的效率,Python設計了大量的
記憶體池機制,減少運行期間malloc和free的操作。

>>> from sys import getrefcount>>> a = [1,2,3]>>> getrefcount(a)2>>> b =a>>> getrefcount(a)3>>>
標記-清除機制

引用計數機制有個致命的弱點,就是可能存在循環參考的問題:
一組對象的引用計數都不為0,然而這些對象實際上並沒有被任何外部變數引用,它們之間只是相互引用,這意味這個不會
有人使用這組對象,應該回收這些對象所佔的記憶體,然後由於互相引用的存在, 每個對象的引用計數都不為0,因此這些對象
所佔用的記憶體永遠不會被回收。
標記-清除機制就是為瞭解決循環參考的問題。首先只有container對象之間才會產生循環參考,所謂container對象即是內部
可持有對其他對象的引用的對象,比如list、dict、class等,而像PyIntObject、PyStringObject這些是絕不可能產生循環參考的
所以Python的記憶體回收機制運行時,只需要檢查這些container對象,為了跟蹤每個container,需要將這些對象組織到一個集合中。
Python採用了一個雙向鏈表,所以的container對象在建立之後,就會被插入到這個鏈表中。這個鏈表也叫作可收集對象鏈表。

為瞭解決循環參考的問題,提出了有效引用計數的概念,即循環參考的兩個對象引用計數不為0,實際上有效引用計數為0
假設兩個對象為A、B,我們從A出發,因為它有一個對B的引用,則將B的引用計數減1;然後順著引用達到B,因為B有一個對A的引用,
同樣將A的引用減1,這樣,就完成了循環參考對象間環摘除。但是這樣直接修改真實的引用計數,可能存在懸Null 參考的問題。
所以採用修改計數計數副本的方法。
這個計數副本的唯一作用是尋找root object集合(該集合中的對象是不能被回收的)。當成功尋找到root object集合之後,
我們就可以從root object出發,沿著引用鏈,一個接一個的標記不能回收的記憶體。首先將現在的記憶體鏈表一分為二,
一條鏈表中維護root object集合,成為root鏈表,而另外一條鏈表中維護剩下的對象,成為unreachable鏈表。之所以要剖成兩個鏈表,
是基於這樣的一種考慮:現在的unreachable可能存在被root鏈表中的對象,直接或間接引用的對象,這些對象是不能被回收的,
一旦在標記的過程中,發現這樣的對象,就將其從unreachable鏈表中移到root鏈表中;當完成標記後,unreachable鏈表中剩下
的所有對象就是名副其實的垃圾對象了,接下來的記憶體回收只需限制在unreachable鏈表中即可。

分代回收

分代回收的思想:將系統中的所有記憶體塊根據其存活時間劃分為不同的集合,每一個集合就稱為一個“代”
垃圾收集的頻率隨著“代”的存活時間的增大而減小,也就是說,活的越長的對象,就越可能不是垃圾,就應該
越少去收集。當某一代對象經曆過記憶體回收,依然存活,那麼它就被歸入下一代中。
在Python中總共有三個“代”,每個代其實就是上文中所提到的一條可收集對象鏈表。下面的數組就是用於分代
垃圾收集的三個“代”。

#define NUM_GENERATIONS 3#define GEN_HEAD(n) (&generations[n].head)// 三代都放到這個數組中/* linked lists of container objects */static struct gc_generation generations[NUM_GENERATIONS] = {/* PyGC_Head, threshold, count */{{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0}, //700個container, 超過立即觸發記憶體回收機制{{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0}, // 10個{{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0}, // 10個};PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);

其中存在三個閾值,分別是700,10,10
可以通過get_threshold()方法獲得閾值:

import gcprint(gc.get_threshold())(700, 10, 10) 

其中第一個閾值表示第0代鏈表最多可以容納700個container對象,超過了這個極限值,就會立即出發記憶體回收機制。

後面兩個閾值10是分代有關係,就是每10次0代記憶體回收,會配合1次1代的記憶體回收;而每10次1代的記憶體回收,
才會有1次的2代記憶體回收。也就是空間換時間的體現。


記憶體回收的流程:
--> 分配記憶體的時候發現超過閾值(第0代的container個數),觸發記憶體回收
--> 將所有可收集對象鏈表放在一起(將比當前處理的“代”更年輕的"代"的鏈表合并到當前”代“中)
--> 計算有效引用計數
--> 根據有效引用計數分為計數等於0和大於0兩個集合
--> 引用計數大於0的對象,放入下一代
--> 引用計數等於0的對象,執行回收
--> 回收遍曆容器內的各個元素, 減掉對應元素引用計數(破掉循環參考)
--> python底層記憶體管理機制回收記憶體

參考文檔:
http://www.cnblogs.com/vamei/p/3232088.html
http://python.jobbole.com/83548/
http://python.jobbole.com/82061/
python源碼剖析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.