標籤:需要 工作 list idp 源碼 賦值 分享 允許 有一個
說到記憶體管理,就先說一下記憶體回收吧。記憶體回收是Python,Java等語言管理記憶體的一種方式,說的直白些,就是清除無用的垃圾對象。C語言及C++中,需要通過malloc來進行記憶體的申請,通過free而進行記憶體的釋放。而Python和Java中有自動的記憶體管理機制,不需要動態釋放記憶體,這種機制就是記憶體回收。
Python中通過引用計數法來進行記憶體的管理的。對每一個對象,都維護這一個對指向該對對象的引用的計數。比如:
a = "ABC"
b = a
首先建立了一個字串對象"ABC",然後將字串對象的引用賦值為a。因而,字串對象"ABC"的引用計數會加1。當執行b=a的時候,僅僅是建立了指向"ABC"這個字串對象的引用的別名,而沒有建立對象。這樣,字串對象"ABC"的引用計數會加1。如何驗證呢?我們可以查看a和b的id是否一致即可:
a = "ABC"
b = a
id(a) 36422672
id(b) 36422672
引用計數的數目可以通過sys.getrefcount()函數來進行擷取。為啥建立a對象後引用計數是2而不是1呢?這是因為一個是全域域裡的,一個是調用函數的。當執行完b=a後,引用計數加1。
import sys
a = "ABC"
sys.getrefcount(a) 2
b = a
sys.getrefcount(a) 3
那什麼時候引用計數增加,什麼時候引用計數減少呢?小編總結了一下:
引用計數增加主要有以下情境:
1、對象被建立時:a = "ABC";
2、另外的別人被建立時:b = a;
3、被作為參數傳遞給函數時:foo(a);
4、作為容器物件(list,元組,字典等)的一個元素時:x = [1,a,‘33‘]。
引用計數減少主要有以下情境:
1、一個本地引用離開了它的範圍時,比如上面的foo(a)函數結束時,a指向的對象引用減1;
2、對象的引用被顯式的銷毀時:del a 或者 del b;
3、對象的引用被賦值給其他對象時:a = 789;
4、對象從一個容器物件(例如list)中移除時:L.remove(a);
5、容器物件本身被銷毀:del L,或者容器物件本身離開了範圍。
所謂記憶體回收,就是回收引用計數為0的對象,釋放其佔用的記憶體空間。記憶體回收機制還有一個迴圈記憶體回收行程, 確保釋放循環參考對象(a引用b, b引用a, 導致其引用計數永遠不為0)。
Python的記憶體管理是階層的,各層負責不同的功能,如所示:-1,-2層主要有作業系統進行操作,屬於核心態;第0層是C中的malloc,free等記憶體配置和釋放函數進行操作;第1層和第2層是記憶體池,有Python的介面函數PyMem_Malloc函數實現,當對象小於256K時由該層直接分配記憶體;第3層是最上層,也就是我們對Python對象的直接操作。感興趣的同學可以閱讀以下《Python源碼剖析》,相信你的收穫肯定是大大滴。
在 C 中如果頻繁的調用 malloc 與 free 時會產生效能問題的,再加上頻繁的分配與釋放小塊的記憶體會產生記憶體片段。Python 在這裡主要乾的工作有:如果請求分配的記憶體在1~256位元組之間就使用自己的記憶體管理系統,否則直接使用 malloc。這裡還是會調用 malloc 分配記憶體,但每次會分配一塊大小為256k的大塊記憶體。經由記憶體池登記的記憶體到最後還是會回收到記憶體池,,並不會調用 C 的 free 釋放掉,以便下次使用。
Python中的變數在記憶體中的分配主要有兩種,一種是簡單拷貝,比如上一篇講述的list,改變一個就會引起另一個的改變,這是因為它們的引用相同,指向了同一個對象:
L= [3,4,5]
LL = L
id(L) 34432584
id(LL) 34432584
L.append(6)
id(L) 34432584
id(LL) 34432584
另外一種是深度拷貝,如數值、字串、元組(tuple不允許被更改)等,也就是說當將變數a賦值給變數b時,雖然a和b的記憶體空間仍然相同,但當a的值發生變化時,會重新給a分配空間,a和b的地址變得不再相同。
a = ‘ABC‘
b = a
id(a) 36042680
id(b) 36042680
a = ‘XYZ‘
id(a) 36043424
id(b) 36042680
Python記憶體管理機制