python之【python的內存管理機制】

想要了解python,就必須要了解Python的內存管理機制,不然我們就會經常踩進一些莫名其妙的坑!

Python的內存管理機制共分爲三部分:1、引用計數 2、垃圾回收 3、內存池機制

在瞭解以上三部分內容之前,我們先來了解一下python的變量與對象:

在這裏插入圖片描述
我們可以簡單的把python的變量理解爲指向對象的一個指針,這個指針指向了對象在內存中的真正存儲位置,從而通過這個變量指針獲取對象的值。而python對象是類型已知的、明確的內存數據或者內存空間,內存空間中存儲了它們所表示的值,如果不理解的話,我們舉個例子:

>>> a = '123'

這裏,真正的對象是’123’字符串,而a只是指向這個字符串內存空間的一個指針,通過賦值’=’,我們把變量a和對象’123’之間建立了連接關係或者映射關係,就是我們所說的"引用",所以我們可以通過這個指針a來獲取對象’123’的值。

從中,我們也可以看出變量名其實是沒有類型的,類型是屬於對象的,由於變量引用了對象,所以變量的類型就是所引用對象的類型!

我們可以通過python的內置函數id(),來查看變量所引用對象的id,也就是內存地址:

>>> a = 1
>>> b = a
>>> print(id(a), id(b))
2193993458056

同時也可以使用內置關鍵字 is 來判斷兩個變量是否引用同一個對象:

>>> a = '123'
>>> b = '123'
>>> print(a is b)
True

一、引用計數

接下來我們來介紹一下python的引用計數:

在python中,每個對象都會包含一個頭部信息,這個頭部信息包括:類型標識符和引用計數器!

查看對象的引用計數可以調用 sys.getrefcount():

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

這裏需要注意的是,第一次把某個引用作爲參數傳遞給 sys.getrefcount() 時,會臨時創建一個該參數的臨時引用,所以我們看到第一次調用時發現比實際的多1

引用計數增加的方式:

1、對象的創建

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

2、引用的賦值

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

3、作爲容器對象的一個元素

>>> c = [1, 2, [1, 2]]
>>> sys.getrefcount([1, 2])
4

4、作爲參數傳遞給函數

>>> foo(a)
>>> sys.getrefcount([1, 2])
5

引用計數減少的方式:

1、顯示得銷燬對象引用

>>> del a
>>> sys.getrefcount([1, 2])
4

2、該對象的引用被賦值了其它對象

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

3、從容器對象中移除

>>> c.remove([1, 2])
>>> sys.getrefcount([1, 2])
2

4、引用離開作用域,比如函數foo()結束返回


二、 垃圾回收

python垃圾回收的原理:

當python對象的引用計數爲0時,python解釋器會對這個對象進行垃圾回收。

但需要注意的是在垃圾回收的時候,python不能進行其它任務,所以如果頻繁的進行垃圾回收將大大降低python的工作效率,因此,python只會在特定的條件下自動進行垃圾回收,這個條件就是"閾值",在python運行過程中,會記錄對象的分配和釋放次數,當這兩個次數的差值高於閾值的時候,python纔會進行垃圾回收。

查看閾值:

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

700就是垃圾回收啓動的閾值,後面的兩個10是什麼呢?它們是python的垃圾分代回收機制。爲了處理如list、dict、tuple等容器對象的循環引用問題,python引用了標記-清除和分代回收的策略。

【標記清除】是一種基於追蹤回收(tracing GC)技術實現的回收算法,它分爲兩個階段:第一階段是把所有活動對象打上標記,第二階段是把沒有標記的非活動對象進行回收,對象是否活動的判斷方法是:從根對象觸發,沿着"有向邊"遍歷所有對象,可達的對象就會被標記爲活動對象,不可達的對象就是後面需要清除的對象,如下圖

在這裏插入圖片描述
從根對象(小黑點)出發,1、2、3可達,4、5不可達,那麼1、2、3就會被標記爲活動對象,4、5就是會被回收的對象,這種方法的缺點是:每次清除非活動對象前都要掃描整個堆內存裏面的對象。

【分代回收】是一種以空間換時間的操作方式,python把所有對象的存貨時間分爲3代(0、1、2),對應着3個鏈表,新創建的對象會被移到第0代,當第0代的鏈表總數達到上限時,就會觸發python的垃圾回收機制,把所有可以回收的對象回收,而不會回收的對象就會被移到1代,以此類推,第2代的對象是存活最久的對象,當然分代回收是建立在標記清除技術的基礎上的。

現在回過頭來分析之前的閾值:

>>> gc.get_threshold()
(700, 10, 10)

第一個10代表每10次0代的垃圾回收纔會觸發1次1代的垃圾回收,每10次1代的垃圾回收纔會觸發1次2代的垃圾回收。當然,也可以手動垃圾回收:

>>> gc.collect()
2

三、 python 內存池機制

爲了避免頻繁的申請和釋放內存,python的內置數據類型,數值、字符串,查看python源碼可以看到數值緩存範圍爲 -5 ~ 257

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

對於 -5 ~ 257 範圍內的數值,創建之後python會把其加入到緩存池中,當再次使用時,則直接從緩存池中返回,而不需要重新申請內存,如果超出了這個範圍的數值,則每次都需要申請內存。下面看個例子:

>>> a = 66
>>> b = 66
>>> id(a) == id(b)
True

>>> x = 300
>>> y = 300
>>> id(x) == id(y)
False

字符串的 intern 機制

Python 解釋器中使用了 intern (字符串駐留)的技術來提高字符串效率,所謂 intern 機制,指的是:字符串對象僅僅會保存一份,放在一個共用的字符串儲蓄池中,並且是不可更改的,這也決定了字符串時不可變對象。

機制原理:

實現 Intern 機制的方式非常簡單,就是通過維護一個字符串儲蓄池,這個池子是一個字典結構,如果字符串已經存在於池子中就不再去創建新的字符串,直接返回之前創建好的字符串對象,如果之前還沒有加入到該池子中,則先構造一個字符串對象,並把這個對象加入到池子中去,方便下一次獲取。

但並非全部的字符串都會採用 intern 機制,只有包括下劃線、數字、字母的字符串纔會被 intern,同時字符數不能超過20個,因爲如果超過20個字符的話,Python 解釋器就會認爲這個字符串不常用,不用放入字符串池子中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章