Python中的內存管理

jopen 9年前發布 | 21K 次閱讀 Python Python開發

引用計數和垃圾回收機制:http://www.cnblogs.com/vamei/p/3232088.html

內存池機制:http://developer.51cto.com/art/201007/213585.htm

python的內存管理機制主要包括引用計數機制、垃圾回收機制、內存池機制。

引用計數機制:

    對于常見的賦值語句,如a=1。整數1為對象,而a是一個引用。利用賦值語句,引用a指向對象1.可以使用內置函數id()返回對象的內存地址。在 python中,整數和短小的字符,python都會緩存這些對象,以便重復使用。長的字符串和其他對象可以有多個相同的對象,可以使用賦值語句創建出新的對象。當我們創建多個等于1的引用時,實際上是讓所有這些引用指向同一個對象。為了檢驗兩個引用指向同一個對象,我們可以用is關鍵字。is用于判斷兩個引用所指向的對象是否相同。

    在python中,每個對象都有存有指向該對象的引用總數,及引用計數。我們可以使用sys包中的getrefcount()來查看某個對象的引用計數。需要注意的是,當使用某個引用作為參數傳遞給getrefcount()時,參數實際上創建了一個臨時的引用。因此,getrefcount()所得到的結果,會比期望的多1.

    python的一個容器對象,比如列表,詞典等,可以包括多個對象。實際上,容器對象中包含的并不是元素對象本身,是指向各個元素對象的引用。

    某個對象的引用計數可能減少。比如,可以使用del關鍵字刪除某個引用:>>>a=[1,2,3]  >>>b=a   >>>del a

del也可以用于刪除容器元素中的元素,比如:del a[0]

    如果某個引用指向對象A,當這個引用被重新定向到某個其他對象B時,對象A的引用計數減少:

    >>>a=[1,2,3]    >>>b=a     >>>sys.getrefcount(b)   >>>a=1     >>>getrefcount(b)


垃圾回收機制:

   當python中的對象越來越多,它們將占據越來越大的內存,python會啟動垃圾回收機制。從基本原理上,當python的某個對象的引用計數降為0時,說明沒有任何引用指向該對象,該對象就成為要被回收的垃圾了。例如:

>>>a=[1,2,3]

>>>del a

del a后,已經沒有任何引用指向之前建立的[1,2,3]這個表。用戶不可能通過任何方式接觸或者動用這個對象。當垃圾回收啟動時,python掃描到這個引用計數為0的對象,就將它所占據的內存清空。

    垃圾回收時,Python不能進行其它的任務。頻繁的垃圾回收將大大降低Python的工作效率。如果內存中的對象不多,就沒有必要總啟動垃圾回收。所以,Python只會在特定條件下,自動啟動垃圾回收。當Python運行時,會記錄其中分配對象(object allocation)和取消分配對象(object deallocation)的次數。當兩者的差值高于某個閾值時,垃圾回收才會啟動。

    我們可以通過gc模塊的get_threshold()方法,查看該閾值:

import gc
print(gc.get_threshold())

返回(700, 10, 10),后面的兩個10是與分代回收相關的閾值,后面可以看到。700即是垃圾回收啟動的閾值。可以通過gc中的set_threshold()方法重新設置。我們也可以手動啟動垃圾回收,即使用gc.collect()

Python同時采用了分代(generation) 回收的策略。這一策略的基本假設是,存活時間越久的對象,越不可能在后面的程序中變成垃圾。我們的程序往往會產生大量的對象,許多對象很快產生和消失,但也有一些對象長期被使用。出于信任和效率,對于這樣一些“長壽”對象,我們相信它們的用處,所以減少在垃圾回收中掃描它們的頻率。

Python將所有的對象分為0,1,2三代。所有的新建對象都是0代對象。當某一代對象經歷過垃圾回收,依然存活,那么它就被歸入下一代對象。垃圾回收啟動時,一定會掃描所有的0代對象。如果0代經過一定次數垃圾回收,那么就啟動對0代和1代的掃描清理。當1代也經歷了一定次數的垃圾回收后,那么會啟動對0,1,2,即對所有對象進行掃描。

這兩個次數即上面get_threshold()返回的(700, 10, 10)返回的兩個10。也就是說,每10次0代垃圾回收,會配合1次1代的垃圾回收;而每10次1代的垃圾回收,才會有1次的2代垃圾回收。

同樣可以用set_threshold()來調整,比如對2代對象進行更頻繁的掃描。

import gc
gc.set_threshold(700, 10, 5)

孤立的引用環:

引用環的存在會給上面的垃圾回收機制帶來很大的困難。這些引用環可能構成無法使用,但引用計數不為0的一些對象。為了回收這樣的引用環,Python復制每個對象的引用計數,可以記為gc_ref。假設,每個對象i,該計數為gc_ref_i。Python會遍歷所有的對象i。對于每個對象i引用的對象j,將相應的gc_ref_j減1。在結束遍歷后,gc_ref不為0的對象,和這些對象引用的對象,以及繼續更下游引用的對象,需要被保留。而其它的對象則被垃圾回收。

內存池機制:

在Python中,許多時候申請的內存都是小塊的內存,這些小塊內存在申請后,很快又會被釋放,由于這些內存的申請并不是為了創建對象,所以并沒有對象一級的內存池機制。

這就意味著Python在運行期間會大量地執行malloc和free的操作,頻繁地在用戶態和核心態之間進行切換,這將嚴重影響Python的執行效率。為了加速Python的執行效率,Python引入了一個內存池機制,用于管理對小塊內存的申請和釋放。這也就是之前提到的Pymalloc機制。

在Python 2.5中,Python內部默認的小塊內存與大塊內存的分界點定在256個字節,這個分界點由前面我們看到的名為SMALL_REQUEST_THRESHOLD的符號控制。

也就是說,當申請的內存小于256字節時,PyObject_Malloc會在內存池中申請內存;當申請的內存大于256字節時,PyObject_Malloc的行為將蛻化為malloc的行為。當然,通過修改Python源代碼,我們可以改變這個默認值,從而改變 Python的默認內存管理行為。

在一個對象的引用計數減為0時,與該對象對應的析構函數就會被調用。

但是要特別注意的是,調用析構函數并不意味著最終一定會調用free釋放內存空間,如果真是這樣的話,那頻繁地申請、釋放內存空間會使 Python的執行效率大打折扣(更何況Python已經多年背負了人們對其執行效率的不滿)。一般來說,Python中大量采用了內存對象池的技術,使用這種技術可以避免頻繁地申請和釋放內存空間。因此在析構時,通常都是將對象占用的空間歸還到內存池中。

" 這個問題就是:Python的arena從來不釋放pool。這個問題為什么會引起類似于內存泄漏的現象呢。考慮這樣一種情形,申請 10*1024*1024個16字節的小內存,這就意味著必須使用160M的內存,由于Python沒有默認將前面提到的限制內存池的 WITH_MEMORY_LIMITS編譯符號打開,所以Python會完全使用arena來滿足你的需求,這都沒有問題,關鍵的問題在于過了一段時間,你將所有這些16字節的內存都釋放了,這些內存都回到arena的控制中,似乎沒有問題。

但是問題恰恰就在這時出現了。因為arena始終不會釋放它維護的pool集合,所以這160M的內存始終被Python占用,如果以后程序運行中再也不需要160M如此巨大的內存,這點內存豈不是就浪費了?"

Python內存管理規則:del的時候,把list的元素釋放掉,把管理元素的大對象回收到py對象緩沖池里。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!