python垃圾回收機制
?
?
一、什么是垃圾回收機制?
垃圾回收機制(簡稱 GC)是Python解釋器自帶一種機制,專門用來回收不可用的變量值所占用的內存空間
?
二、為什么要用垃圾回收機制?
程序運行過程中會申請大量的內存空間,而對于一些無用的內存空間如果不及時清理的話會導致內存使用殆盡(內存溢出),導致程序崩潰,因此管理內存是一件重要且繁雜的事情,而 python解釋器自帶的垃圾回收機制把程序員從繁雜的內存管理中解放出來。
?
?
python采用的是引用計數機制為主,標記-清除和分代收集兩種機制為輔的策略
- Python的GC模塊主要運用了 引用計數來跟蹤和回收垃圾 。
- 在引用計數的基礎上,還可以通過“標記-清除”解決容器對象可能產生的循環引用的問題。
- 通過分代回收以空間換取時間進一步提高垃圾回收的效率。
引用計數機制 引用計數的缺陷是循環引用的問題 為每個內存對象維護一個引用計數. 當有新的引用指向某對象時就該該對象的引用計數加1,當指向該對象的引用被銷毀時將該對象計數減1,當計數歸零時,就回收該對象所占用的內存資源 標記 - 清除 分兩個步驟 1 、標記:即從眾多的內存對象中區分出不在會被使用的垃圾對象; 2 、清除:把標記的垃圾對象清除.標記的時候需要確定內存對象的集合Root set,集合里的對象都是可以訪問的.如果root set中的對象引用了其他的對象,那么被引用的對象也不能被標記為垃圾對象.然后從root set出發,遞歸遍歷root set能訪問到的所有對象,進行標記為不是垃圾對象.遍歷結束后,沒有被標記的就是垃圾對象 分代收集 根據一個統計學上的結論,如果一個內存對象在某次Mark過程中發現不是垃圾,那么它短期內成為垃圾的可能性就很小。
分代收集將那些在多次垃圾收集過程中都沒有被標記為垃圾對象的內存對象集中到另外一個區域——年老的區域,即這個區域中的內存對象年齡比較大。
因為年老區域內內存對象短期內變成垃圾的概率很低,所以這些區域的垃圾收集頻率可以降低,相對的,對年輕區域內的對象進行高頻率的垃圾收集。這樣可以提高垃圾收集的整體性能。
?
?
引用計數機制
在 CPython中,大多數對象的生命周期都是通過對象的引用計數來管理的。引用計數是一種最直觀、最簡單的垃圾收集計數,與其他主流GC算法比較,它的最大優點是實時性,即任何內存,一旦沒有指向它的引用,就會立即被回收。
import sys class test(): def __init__ (self): ''' 初始化對象 ''' print ( ' 對象引用次數: ' , sys.getrefcount(self)) # getrefcount()方法用于返回對象的引用計數 def func(c): print ( ' 對象引用次數: ' ,sys.getrefcount(c)) # getrefcount()方法用于返回對象的引用計數 if __name__ == ' __main__ ' : # 生成對象 a= test() func(a) # 增加引用 b= a func(a) # 銷毀引用對象b del b func(a)
輸出結果:
對象引用次數: 3 對象引用次數: 4 對象引用次數: 5 對象引用次數: 4
導致引用計數 +1的情況
- 對象被創建 : 例如 a=1
- 對象被引用 : 例如 b=a
- 對象被作為參數,傳入到一個函數中,例如 func(a)
- 對象作為一個元素,存儲在容器中,例如 list1=[a,a]
導致引用計數 -1的情況
- 對象的別名被顯式銷毀,例如del a
- 對象的別名被賦予新的對象,例如a=24
- 一個對象離開它的作用域,例如f函數執行完畢時,func函數中的局部變量(全局變量不會)
- 對象所在的容器被銷毀,或從容器中刪除對象
引用計數機制 的問題
1.在每次內存對象唄引用或者引用被銷毀時都需要修改引用計數,這類操作被稱為 footprint 。引用計數的 footprint 是很高的,使得程序的整體性能受到很大的影響。
2.引用計數機制還存在著一個致命的弱點,即 ? 循環引用 ?(也稱交叉引用)。
# 變量名l1指向列表1,變量名l2指向列表2 l1=[ ' 列表1中的第一個元素 ' ] # 列表1被引用一次 l2=[ ' 列表2中的第一個元素 ' ] # 列表2被引用一次 l1.append(l2) # 把列表2追加到l1中作為第二個元素,列表2的引用計數為2 l2.append(l1) # 把列表1追加到l2中作為第二個元素,列表1的引用計數為2 # l1與l2 print (l1) print (l2) # 如果我們執行del l1,列表1的引用計數=2-1,即列表1不會被回收,同理del l2,列表2的引用計數=2-1,此時無論列表1還是列表2都沒有任何名字關聯,但是引用計數均不為0,所以循環引用是致命的,這與手動進行內存管理所產生的內存泄露毫無區別 要解決這個問題,Python引入了其他的垃圾收集機制來彌補引用計數的缺陷: 1、“標記-清除” 2、“分代回收”
?
?
標記-清除&分代回收
Python引入了其他的垃圾收集機制來彌補引用計數的缺陷
?
標記-清除
容器對象(比如:list,set,dict,class,instance)都可以包含對其他對象的引用,所以都可能產生循環引用。而“標記-清除”計數就是為了解決循環引用的問題。
在了解標記清除算法前,我們需要明確一點, 內存中有兩塊區域:堆區與棧區,在定義變量時,變量名存放于棧區,變量值存放于堆區,內存管理回收的則是堆區的內容 ,詳解如下圖
標記/清除算法的做法 是當有效內存空間被耗盡的時候,就會停止整個程序,然后進行兩項工作,第一項則是標記,第二項則是清除
標記:標記的過程其實就是,遍歷所有的GC Roots對象(棧區中的所有內容或者線程都可以作為GC Roots對象),然后將所有GC Roots的對象可以直接或間接訪問到的對象標記為存活的對象。
清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。
?GC roots對象直接訪問到的對象,插圖如下
用圖形解釋,環引用的例子中的l1與l2,在什么時候啟動標記清除,標記清除的整個過程
?
分代回收
分代:
分代回收的核心思想是: 在多次掃描的情況下,都沒有被回收的變量,gc機制就會認為,該變量是常用變量,gc對其掃描的頻率會降低 ,具體實現原理如下:
分代指的是根據存活時間來為變量劃分不同等級(也就是不同的代)
新定義的變量,放到新生代這個等級中,假設每隔1分鐘掃描新生代一次,如果發現變量依然被引用,那么該對象的權重(權重本質就是個整數)加一,當變量的權重大于某個設定得值(假設為3),會將它移動到更高一級的青春代,青春代的gc掃描的頻率低于新生代(掃描時間間隔更長),假設5分鐘掃描青春代一次,這樣每次gc需要掃描的變量的總個數就變少了,節省了掃描的總時間,接下來,青春代中的對象,也會以同樣的方式被移動到老年代中。也就是等級(代)越高,被垃圾回收機制掃描的頻率越低
回收:
回收依然是使用引用計數作為回收的依據
圖示:
缺點:
例如一個變量剛剛從新生代移入青春代,該變量的綁定關系就解除了,該變量應該被回收,青春代的掃描頻率低于新生代,所以該變量的回收時間被延遲。
?
?參考:http://sh.qihoo.com/pc/9d4c172691445d42d?cota=3&sign=360_e39369d1&refer_scene=so_1
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
