python有向图_Python 中的垃圾回收机制
一、概述
python采用的是引用計數機制為主,標記-清除和分代收集(隔代回收)兩種機制為輔的策略。
現在的高級語言如java,c#等,都采用了垃圾收集機制,而不再是c,c++里用戶自己管理維護內存的方式。自己管理內存極其自由,可以任意申請內存,但如同一把雙刃劍,為大量內存泄露,懸空指針等bug埋下隱患。對于一個字符串、列表、類甚至數值都是對象,且定位簡單易用的語言,自然不會讓用戶去處理如何分配回收內存的問題。
python里也同java一樣采用了垃圾收集機制,不過不一樣的是:
python采用的是引用計數機制為主,標記-清除和分代收集(隔代回收)兩種機制為輔的策略。
二、引用計數機制
引用計數法機制的原理是:每個對象維護一個ob_ref字段,用來記錄該對象當前被引用的次數,每當新的引用指向該對象時,它的引用計數ob_ref加1,每當該對象的引用失效時計數ob_ref減1,一旦對象的引用計數為0,該對象立即被回收,對象占用的內存空間將被釋放。它的缺點是需要額外的空間維護引用計數,這個問題是其次的,不過最主要的問題是它不能解決對象的“循環引用”,因此,也有很多語言比如Java并沒有采用該算法做來垃圾的收集機制。python里每一個東西都是對象,它們的核心就是一個結構體:PyObject
PyObject是每個對象必有的內容,其中ob_refcnt就是做為引用計數。當一個對象有新的引用時,它的ob_refcnt就會增加,當引用它的對象被刪除,它的ob_refcnt就會減少
引用計數為0時,該對象生命就結束了。
引用計數機制的優點:
1、簡單
2、實時性:一旦沒有引用,內存就直接釋放了,不用像其他機制得等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時。
引用計數機制的缺點:
1、維護引用計數消耗資源
2、循環引用
案例:
import sys class A():def __init__(self):'''初始化對象'''print('object born id:%s' %str(hex(id(self))))def f1():'''循環引用變量與刪除變量'''while True:c1=A()del c1def func(c):print('obejct refcount is: ',sys.getrefcount(c)) #getrefcount()方法用于返回對象的引用計數if __name__ == '__main__':#生成對象a=A()func(a)#增加引用b=afunc(a)#銷毀引用對象bdel bfunc(a) #結果 object born id:0x19f5ecb9320 obejct refcount is: 4 obejct refcount is: 5 obejct refcount is: 4導致引用計數+1的情況
- 對象被創建,例如a=23
- 對象被引用,例如b=a
- 對象被作為參數,傳入到一個函數中,例如func(a)
- 對象作為一個元素,存儲在容器中,例如list1=[a,a]
導致引用計數-1的情況
- 對象的別名被顯式銷毀,例如del a
- 對象的別名被賦予新的對象,例如a=24
- 一個對象離開它的作用域,例如:func函數執行完畢時,func函數中的局部變量(全局變量不會)
- 對象所在的容器被銷毀,或從容器中刪除對象
循環引用導致內存泄露
def f2():'''循環引用'''while True:c1=A()c2=A()c1.t=c2c2.t=c1del c1del c2- 創建了c1,c2后,這兩個對象的引用計數都是1,執行c1.t=c2和c2.t=c1后,引用計數變成2.
- 在del c1后,內存c1的對象的引用計數變為1,由于不是為0,所以c1的對象不會被銷毀,同理,在del c2后也是一樣的。
- 雖然它們兩個的對象都是可以被銷毀的,但是由于循環引用,導致垃圾回收器都不會回收它們,所以就會導致內存泄露。
分代回收
- 分代回收是一種以空間換時間的操作方式,Python將內存根據對象的存活時間劃分為不同的集合,每個集合稱為一個代,Python將內存分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代),他們對應的是3個鏈表,它們的垃圾收集頻率隨著對象存活時間的增大而減小。
- 新創建的對象都會分配在年輕代,年輕代鏈表的總數達到上限時,Python垃圾收集機制就會被觸發,把那些可以被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推,老年代中的對象是存活時間最久的對象,甚至是存活于整個系統的生命周期內。
- 同時,分代回收是建立在標記清除技術基礎之上。分代回收同樣作為Python的輔助垃圾收集技術處理那些容器對象
垃圾回收
有三種情況會觸發垃圾回收:
gc模塊
gc模塊提供一個接口給開發者設置垃圾回收的選項。上面說到,采用引用計數的方法管理內存的一個缺陷是循環引用,而gc模塊的一個主要功能就是解決循環引用的問題。
常用函數:
顯式進行垃圾回收,可以輸入參數,0代表只檢查第一代的對象,1代表檢查一,二代的對象,2代表檢查一,二,三代的對象,如果不傳參數,執行一個full collection,也就是等于傳2。返回不可達(unreachable objects)對象的數目。
設置自動執行垃圾回收的頻率。
gc實踐案例
def f3():'''循環引用'''while True:c1=A()c2=A()c1.t=c2c2.t=c1del c1del c2#增加垃圾回收機制print(gc.garbage)print(gc.collect())print(gc.garbage)time.sleep(10) #結果 object born id:0x21d1a5dc470 object born id:0x21d1a5dc9e8 [] 4 gc: collectable <A 0x0000021D1A5DC470> [<__main__.A object at 0x0000021D1A5DC470>, <__main__.A object at 0x0000021D1A5DC9E8>, {'t': <__main__.A object at 0x0000021D1A5DC9E8>}, {'t': <__main__.A object at 0x0000021D1A5DC470>}] gc: collectable <A 0x0000021D1A5DC9E8> gc: collectable <dict 0x0000021D1A156C88> gc: collectable <dict 0x0000021D1A5CABC8>gc模塊的自動垃圾回收機制
必須要import gc模塊,并且is_enable()=True才會啟動自動垃圾回收。
這個機制的主要作用就是發現并處理不可達的垃圾對象。
在Python中,采用分代收集的方法。把對象分為三代,一開始,對象在創建的時候,放在一代中,如果在一次一代的垃圾檢查中,該對象存活下來,就會被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會被放到三代中。
gc模塊里面會有一個長度為3的列表的計數器,可以通過gc.get_count()獲取。
def f4():'''垃圾自動回收'''print(gc.get_count())a=A()print(gc.get_count())del aprint(gc.get_count()) #結果 (621, 10, 0) object born id:0x2ca32a8c588 (624, 10, 0) (623, 10, 0)- 621指距離上一次一代垃圾檢查,Python分配內存的數目減去釋放內存的數目,注意:是內存分配,而不是引用計數的增加。
- 10指距離上一次二代垃圾檢查,一代垃圾檢查的次數。
- 0是指距離上一次三代垃圾檢查,二代垃圾檢查的次數。
自動回收閾值
gc模快有一個自動垃圾回收的閥值,即通過gc.get_threshold函數獲取到的長度為3的元組,例如(700,10,10)
每一次計數器的增加,gc模塊就會檢查增加后的計數是否達到閥值的數目,如果是,就會執行對應的代數的垃圾檢查,然后重置計數器
注意:
如果循環引用中,兩個對象都定義了__del__方法,gc模塊不會銷毀這些不可達對象,因為gc模塊不知道應該先調用哪個對象的__del__方法,所以為了安全起見,gc模塊會把對象放到gc.garbage中,但是不會銷毀對象。
標記清除
標記清除(Mark—Sweep)』算法是一種基于追蹤回收(tracing GC)技術實現的垃圾回收算法。它分為兩個階段:第一階段是標記階段,GC會把所有的『活動對象』打上標記,第二階段是把那些沒有標記的對象『非活動對象』進行回收。那么GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?
對象之間通過引用(指針)連在一起,構成一個有向圖,對象構成這個有向圖的節點,而引用關系構成這個有向圖的邊。從根對象(root object)出發,沿著有向邊遍歷對象,可達的(reachable)對象標記為活動對象,不可達的對象就是要被清除的非活動對象。根對象就是全局變量、調用棧、寄存器。 mark-sweepg 在上圖中,我們把小黑圈視為全局變量,也就是把它作為root object,從小黑圈出發,對象1可直達,那么它將被標記,對象2、3可間接到達也會被標記,而4和5不可達,那么1、2、3就是活動對象,4和5是非活動對象會被GC回收。
標記清除算法作為Python的輔助垃圾收集技術主要處理的是一些容器對象,比如list、dict、tuple,instance等,因為對于字符串、數值對象是不可能造成循環引用問題。Python使用一個雙向鏈表將這些容器對象組織起來。不過,這種簡單粗暴的標記清除算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描所有對象。
總結
以上是生活随笔為你收集整理的python有向图_Python 中的垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python虚拟环境的安装和配置_基于v
- 下一篇: python office自动化_Pyt