筆記-python-記憶體管理
a = 1
1是乙個物件,a是引用,指向1。
>>> id(a)
這個數字代表記憶體位址;
在python中,整數和短小的字元,python都會快取這些物件,以便重複使用。當我們建立多個等於1的引用時,實際上是讓所有這些引用指向同乙個物件。
下面進行測試:
a = 1
b = 1
print(id(a), id(b), a is b)
1951821280 1951821280 true
這點需要特別注意,特別是在多重陣列建立時;
在python中,每個物件都有存有指向該物件的引用總數,即引用計數(reference count)
可以使用sys包中的getrefcount(),來檢視某個物件的引用計數。需要注意的是,當使用某個作為引數,傳遞給getrefcount()時,引數實際上建立了乙個臨時的引用;所以,getrefcount()所得到的結果,會比期望的多1.
from sys import getrefcount
a = [1, 2, 3]
print(getrefcount(a))
b = a
print(getrefcount(b))
輸出是2和3,而非1和2
python的乙個容器物件(container),比如表、詞典等,可以包含多個物件,實際上,容器物件中包含的並不是元素物件本身,而是指向各個元素物件的引用。
class from_obj(object):
def __init__(self, to_obj):
self.to_obj = to_obj
b = [1,2,3]
a = from_obj(b)
print(id(a.to_obj))
print(id(b))
結果是顯然,a引用了b。
當乙個物件a被另乙個物件b引用時,a的引用計數將增加1.
容器的引用可能構成複雜的拓撲。
引用環:reference cycle兩個物件可能相互引用,構成引用環。
a =
b = [a]
即使是乙個物件,只需要自己引用自己,也能構成引用環。
a =
print(getrefcount(a))
引用環會給垃圾**機制帶來很大的麻煩。
當然,引用計數是會減少的,比如,刪除引用;
from sys import getrefcount
a = [1, 2, 3]
b = a
print(getrefcount(b))
del a
print(getrefcount(b))
或者,引用指向其它物件;
from sys import getrefcount
a = [1, 2, 3]
b = a
print(getrefcount(b))
a = 1
print(getrefcount(b))
記憶體不是無限的,很多物件也不會一直占用記憶體,因此,在合適的時候需要記憶體**garbage colleciton;
python中的垃圾**是以引用計數為主,分代收集為輔。引用計數的缺陷是迴圈引用的問題。
在python中,如果乙個物件的引用數為0,python虛擬機器就會**這個物件的記憶體。
**垃圾無疑需要占用處理能力,垃圾**時,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()。
導致引用計數+1的情況
物件被建立,例如a=23
物件被引用,例如b=a
物件被作為引數,傳入到乙個函式中,例如func(a) 物件作為引數傳入到乙個函式中會 +2
物件作為乙個元素,儲存在容器中,例如list1=[a,a]
導致引用計數-1的情況
物件的別名被顯式銷毀,例如del a
物件的別名被賦予新的物件,例如a=24
乙個物件離開它的作用域,例如f函式執行完畢時,func函式中的區域性變數(全域性變數不會)
物件所在的容器被銷毀,或從容器中刪除物件
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的一些物件。
a =
b = [a]
del a
del b
從需求來看,上面兩個物件不再使用,應該釋放對應的資源;但由於引用環,這兩個物件的引用計數都沒有降到0,不會被**;
為了**這樣的引用環,python複製每個物件的引用計數,可以記為gc_ref。假設,每個物件i,該計數為gc_ref_i。python會遍歷所有的物件i。對於每個物件i引用的物件j,將相應的gc_ref_j減1。
在結束遍歷後,gc_ref不為0的物件,和這些物件引用的物件,以及繼續更下游引用的物件,需要被保留。而其它的物件則被垃圾**。
記憶體管理筆記
mrc手動記憶體管理 1.系統不會去檢查已釋放的物件,也就是說,當乙個物件的引用計數為0時,這個物件此時再呼叫其方法不會報錯,成為野指標 除非開啟殭屍除錯診斷,一旦開啟殭屍診斷就會發現崩潰。2.當把這個物件賦值為nil時,成為空指標 再呼叫其方法,將不會出錯,更不會崩潰。3.dealloc方法必須含...
python 記憶體分析 python記憶體管理分析
記憶體管理,對於python這樣的動態語言,是至關重要的一部分,它在很大程度上甚至決定了python的執行效率,因為在python的執行中,會建立和銷毀大量的物件,這些都涉及到記憶體的管理。小塊空間的記憶體池 在python中,許多時候申請的記憶體都是小塊的記憶體,這些小塊記憶體在申請後,很快又會被...
python 記憶體管理
記憶體管理,對於python這樣的動態語言,是至關重要的一部分,它在很大程度上甚至決定了python的執行效率,因為在python的執行中,會建立和銷毀大量的物件,這些都涉及到記憶體的管理。小塊空間的記憶體池 在python中,許多時候申請的記憶體都是小塊的記憶體,這些小塊記憶體在申請後,很快又會被...