深入Python底層,談談內存管理機制

深入Python底層,談談內存管理機制

說到內存管理,就先說一下垃圾回收吧。垃圾回收是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源碼剖析》,相信你的收穫肯定是大大滴。

深入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


分享到:


相關文章: