【Python】內存管理機制

1、垃圾回收
2、緩存機制

1、垃圾回收
垃圾回收機制:
引用計數
標記清除
分帶回收

描述垃圾回收之前先引入一個概念:refchain雙向環狀鏈表。這個鏈表的作用很強大,python內部會維護一個這樣的鏈表,當python程序中一旦創建對象,都會把創建的對象添加到refchain中,這個鏈表保存了所有創建的對象,並且每個對象都包含了上下對象的指針、引用計數和對象的類型。

引用計數:
在refchain雙向環狀鏈表中所有的對象內部都有一個自己的引用計數器,表示該對象被引用的次數。當一個python的對象被變量引用時,引用計數+1,不在被變量引用時,引用計數-1,當對象的引用計數爲0時,對象就會被當做垃圾回收掉(對象從refchain鏈表中摘除,釋放內存(不考慮緩存等特殊情況))

但引用計數會存在一個問題:循環引用

循環引用:兩個對象相互引用。
場景:如果沒有變量去引用兩個對象,並且兩個對象存在循環引用,那麼這兩個對象的引用計數不爲0,導致它們永遠不會被使用或者銷燬

存在循環引用的對象:
列表
元組
字典
集合

自定義類等能進行數據嵌套的類型
後果:項目中如果存在大量這種對象,就會導致內存一直被消耗,直到內存被耗盡,程序崩潰;當重啓系統的時候內存又會空閒很多。
解決辦法:引入標記清除和分帶回收
標記清除:
創建特殊鏈表專門用於保存列表、元組、字典、集合、自定義類等對象,之後再去檢查這個鏈表中的對象是否存在循環引用,如果存在則讓雙發的引用計數-1,當引用計數爲0時,則是垃圾,進行回收。否則不是垃圾。

分帶回收:(閾值)
對標記清除的鏈表進行優化,將那些可能存在循環引用的對象拆分到3個鏈表中,鏈表分爲:0/1/2三代,每代都可以存儲對象和閾值,當達到閾值時,就會對相應的鏈表中的每個對象做一次掃描。掃描的結果是:如果對象存在循環引用,則對象的引用計數都-1,爲0的話則視爲垃圾回收,如果不是垃圾,則將對象放入下一代鏈表中。

0/1/2三代的掃描時機和閾值:

0代:當0代鏈表的對象個數達到700個時會掃描一次
1代:當0代鏈表掃描10次時掃描一次
2代:當1代鏈表掃描10次時掃描一次

2、python緩存機制
可以從上文了解到如果對象的引用計數爲0時,就會被銷燬並釋放內存。實際並不是這樣子,因爲頻繁地創建和銷燬對象的代價很大並且會影響程序的執行效率。爲了解決這個問題,python中引入了 緩存機制 。

例如:引用計數爲0時,並不會真正的銷燬對象,而是將它放到一個鏈表中,之後再創建對象時不會再重新開闢新的內存,而是在這個鏈表中將之前的對象進行重新賦值來使用

不同的數據類型,有不同的緩存機制。

2.1 池(int)
爲了避免重複創建和銷燬一些常見的對象,池中會初始化[-5~257]範圍的整型對象,創建這些對象時,直接從池中獲取對象並不會重新開闢新的內存

2.2 free_list(float、list、tuple、dict)
當一個對象的引用計數爲0時,按理說應該回收,但內部不會回收,而是將對象添加到free_list鏈表中當緩存,以後再創建對象對應的類型時,不再開闢新內存,而是直接從free_list中獲取

數據類型 free_list緩存對象個數
float 100
tuple 20
dict 80
list 80
對於tuple,存儲的20個元組對象特點爲:第一個爲空元組、第二個爲元素只有一個的元組,接下來以此類推…

3、調優手段
手動垃圾回收
避免循環應用
調高垃圾回收閾值

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