整數在程序中的使用很是普遍,Python爲了優化速度,使用了小整數對象池, 避免爲整數頻繁申請和銷燬內存空間。java
Python 對小整數的定義是 [-5, 257) 這些整數對象是提早創建好的,不會被垃圾回收。在一個 Python 的程序中,全部位於這個範圍內的整數使用的都是同一個對象.node
同理,單個字母也是這樣的。python
可是當定義2個相同的字符串時,引用計數爲0,觸發垃圾回收c++
小整數程序員
在 -5 - 256 之間 對象的引用的是 同一個對象web
每個大整數,均建立一個新的對象(超太小整數的範圍)算法
每當建立一個大整數的時候,都會新建一個對象,可是這個對象再也不使用的時候,並不會銷燬,後面再創建的對象會複用以前已經再也不使用的對象的內存空間。(這裏的再也不使用指的是引用計數爲0,能夠被銷燬)編程
python會不會建立9個對象呢?在內存中會不會開闢9個」HelloWorld」的內存空間呢? 想一下,若是是這樣的話,咱們寫10000個對象,好比a1=」HelloWorld」…..a1000=」HelloWorld」, 那他豈不是開闢了1000個」HelloWorld」所佔的內存空間了呢?若是真這樣,內存不就爆了嗎?因此python中有這樣一個機制——intern機制
,讓他只佔用一個」HelloWorld」所佔的內存空間。靠引用計數去維護什麼時候釋放。c#
逐行解釋」?api
其實在CPython的交互式解釋器(例如python命令不指定參數時)裏,每輸入一行能夠當即執行的代碼,Python就會把它看成一個編譯單元來編譯到字節碼並解釋執行;若是輸入的代碼還沒有構成一個完整的單元,例如函數聲明或者類聲明,則等到得到了完整單元的輸入後再看成一個編譯單元來處理。
因此當咱們在CPython的交互式解釋器中分別輸入"a = 10.1"、"b = 10.1"這兩行時,它們分別被看成一個編譯單元處理,其中的常量池沒有共享,常量池項也都是各自新建立的,因此會獲得a is b爲False的結果。 而在同一環境裏輸入"(10.1) is (10.1)"時,這一行被看做一個編譯單元,其中兩次對10.1這個常量的使用都變成了對同一對象的引用,於是is的結果爲True。
當使用python命令去總體解釋一個Python源碼文件時,其中位於頂層的
兩行代碼就會處於同一個編譯單元中,於是共享常量池,於是a is b就會是True。
就這樣嗯。原本對這種應該是值語義的對象就不該該關注其identity,否則「樂趣多多」…
如今的高級語言如java,c#等,都採用了垃圾收集機制,而再也不是c,c++裏用戶本身管理維護內存的方式。本身管理內存極其自由,能夠任意申請內存,但如同一把雙刃劍,爲大量內存泄露,懸空指針等bug埋下隱患。 對於一個字符串、列表、類甚至數值都是對象,且定位簡單易用的語言,天然不會讓用戶去處理如何分配回收內存的問題。 python裏也同java同樣採用了垃圾收集機制,不過不同的是: python採用的是引用計數機制爲主, 分代收集兩種機制爲輔的策略
python裏每個東西都是對象,它們的核心就是一個結構體:PyObject
PyObject是每一個對象必有的內容,其中ob_refcnt就是作爲引用計數。當一個對象有新的引用時,它的ob_refcnt就會增長,當引用它的對象被刪除,它的ob_refcnt就會減小
當引用計數爲0時,該對象生命就結束了。
維護引用計數消耗資源
循環引用
list1與list2相互引用,若是不存在其餘對象對它們的引用,list1與list2的引用計數也仍然爲1,所佔用的內存永遠沒法被回收,這將是致命的。 對於現在的強大硬件,缺點1尚可接受,可是循環引用致使內存泄露,註定python還將引入新的回收機制。(標記清除和分代收集)
英文原文: visualizing garbage collection in ruby and python 中文:畫說 Ruby 與 Python 垃圾回收
本文基於我在剛剛過去的在布達佩斯舉行的RuPy上的演講。我以爲趁熱打鐵寫成帖子應該會比只留在幻燈片上更有意義。你也能夠看看演講錄像。再跟你說件事,我在Ruby大會也會作一個類似的演講,可是我不會去說Python的事兒,相反我會對比一下MRI,JRuby和Rubinius的垃圾回收機制。 想了解Ruby垃圾回收機制和Ruby內部實現更詳盡的闡述,請關注即將問世的拙做 《Ruby Under a Microscope》。
若是將算法和業務邏輯比做應用程序的大腦,垃圾回收對應哪一個器官呢?
既然是"Ruby Python"大會,我以爲對比一下Ruby和Python的垃圾回收機制應該會頗有趣。在此以前,到底爲何要計較垃圾回收呢?畢竟,這不是什麼光鮮亮麗激動人心的主題,對吧。大家你們有多少人對垃圾回收感冒?(居然有很多RuPyde與會者舉手了!)
最近Ruby社區發表了一篇博文,是關於如何經過更改Ruby GC設置來爲單元測試提速的。我認爲這篇文章是極好的。對於想讓單元測試跑得更快和讓程序GC暫停更少的人來講頗有裨益,可是GC並沒能引發個人興趣。第一瞥GC就像是一個讓人昏昏欲睡的、乾巴巴的技術主題。
可是實際上垃圾回收是一個迷人的主題:GC算法不只是計算機科學史的重要組成部分,也是一個前沿課題。舉例來講,MRI Ruby使用的標記-清除算法已經年逾五旬了,而Ruby的替代語言Rubinius使用的GC算法在不久前的2008年才被髮明出來。
然而, "垃圾回收"這個詞其實有些用詞不當。
GC系統所承擔的工做遠比"垃圾回收"多得多。實際上,它們負責三個重要任務。它們
若是將應用程序比做人的身體:全部你所寫的那些優雅的代碼,業務邏輯,算法,應該就是大腦。以此類推,垃圾回收機制應該是那個身體器官呢?(我從RuPy聽衆那聽到了很多有趣的答案:腰子、白血球 :) )
我認爲垃圾回收就是應用程序那顆躍動的心。像心臟爲身體其餘器官提供血液和養分物那樣,垃圾回收器爲你的應該程序提供內存和對象。若是心臟停跳,過不了幾秒鐘人就完了。若是垃圾回收器中止工做或運行遲緩,像動脈阻塞,你的應用程序效率也會降低,直至最終死掉。
運用實例一向有助於理論的理解。下面是一個簡單類,分別用Python和Ruby寫成,咱們今天就以此爲例:
順便提一句,兩種語言的代碼竟能如此相像:Ruby 和 Python 在表達同一事物上真的只是略有不一樣。可是在這兩種語言的內部實現上是否也如此類似呢?
當咱們執行上面的Node.new(1)時,Ruby到底作了什麼?Ruby是如何爲咱們建立新的對象的呢? 出乎意料的是它作的很是少。實際上,早在代碼開始執行前,Ruby就提早建立了成百上千個對象,並把它們串在鏈表上,名曰:可用列表。下圖所示爲可用列表的概念圖:
想象一下每一個白色方格上都標着一個"未使用預建立對象"。當咱們調用 Node.new ,Ruby只需取一個預建立對象給咱們使用便可:
上圖中左側灰格表示咱們代碼中使用的當前對象,同時其餘白格是未使用對象。(請注意:無疑個人示意圖是對實際的簡化。實際上,Ruby會用另外一個對象來裝載字符串"ABC",另外一個對象裝載Node類定義,還有一個對象裝載了代碼中分析出的抽象語法樹,等等)
若是咱們再次調用 Node.new,Ruby將遞給咱們另外一個對象:
這個簡單的用鏈表來預分配對象的算法已經發明瞭超過50年,而發明人這是赫赫有名的計算機科學家John McCarthy,一開始是用Lisp實現的。Lisp不只是最先的函數式編程語言,在計算機科學領域也有許多創舉。其一就是利用垃圾回收機制自動化進行程序內存管理的概念。
標準版的Ruby,也就是衆所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法與McCarthy在1960年的實現方式很相似。不管好壞,Ruby的垃圾回收機制已經53歲高齡了。像Lisp同樣,Ruby預先建立一些對象,而後在你分配新對象或者變量的時候供你使用。
咱們已經瞭解了Ruby預先建立對象並將它們存放在可用列表中。那Python又怎麼樣呢?
儘管因爲許多緣由Python也使用可用列表(用來回收一些特定對象好比 list),但在爲新對象和變量分配內存的方面Python和Ruby是不一樣的。
例如咱們用Pyhon來建立一個Node對象:
與Ruby不一樣,當建立對象時Python當即向操做系統請求內存。(Python實際上實現了一套本身的內存分配系統,在操做系統堆之上提供了一個抽象層。可是我今天不展開說了。)
當咱們建立第二個對象的時候,再次像OS請求內存:
看起來夠簡單吧,在咱們建立對象的時候,Python會花些時間爲咱們找到並分配內存。
Ruby把無用的對象留在內存裏,直到下一次GC執行
回過來看Ruby。隨着咱們建立愈來愈多的對象,Ruby會持續尋可用列表裏取預建立對象給咱們。所以,可用列表會逐漸變短:
...而後更短:
請注意我一直在爲變量n1賦新值,Ruby把舊值留在原處。"ABC","JKL"和"MNO"三個Node實例還滯留在內存中。Ruby不會當即清除代碼中再也不使用的舊對象!Ruby開發者們就像是住在一間凌亂的房間,地板上摞着衣服,要麼洗碗池裏都是髒盤子。做爲一個Ruby程序員,無用的垃圾對象會一直環繞着你。
用完的垃圾對象會當即被Python打掃乾淨
Python與Ruby的垃圾回收機制頗爲不一樣。讓咱們回到前面提到的三個Python Node對象:
在內部,建立一個對象時,Python老是在對象的C結構體裏保存一個整數,稱爲 引用數。期初,Python將這個值設置爲1:
值爲1說明分別有個一個指針指向或是引用這三個對象。假如咱們如今建立一個新的Node實例,JKL:
與以前同樣,Python設置JKL的引用數爲1。然而,請注意因爲咱們改變了n1指向了JKL,再也不指向ABC,Python就把ABC的引用數置爲0了。 此刻,Python垃圾回收器馬上自告奮勇!每當對象的引用數減爲0,Python當即將其釋放,把內存還給操做系統:
上面Python回收了ABC Node實例使用的內存。記住,Ruby棄舊對象原地於不顧,也不釋放它們的內存。
Python的這種垃圾回收算法被稱爲引用計數。是George-Collins在1960年發明的,恰巧與John McCarthy發明的可用列表算法在同一年出現。就像Mike-Bernstein在6月份哥譚市Ruby大會傑出的垃圾回收機制演講中說的: "1960年是垃圾收集器的黃金年代..."
Python開發者工做在衛生之家,你能夠想象,有個患有輕度OCD(一種強迫症)的室友一刻不停地跟在你身後打掃,你一放下髒碟子或杯子,有個傢伙已經準備好把它放進洗碗機了!
如今來看第二例子。加入咱們讓n2引用n1:
上圖中左邊的DEF的引用數已經被Python減小了,垃圾回收器會當即回收DEF實例。同時JKL的引用數已經變爲了2 ,由於n1和n2都指向它。
最終那間凌亂的房間充斥着垃圾,再不能歲月靜好了。在Ruby程序運行了一陣子之後,可用列表最終被用光光了:
此刻全部Ruby預建立對象都被程序用過了(它們都變灰了),可用列表裏空空如也(沒有白格子了)。
此刻Ruby祭出另外一McCarthy發明的算法,名曰:標記-清除。首先Ruby把程序停下來,Ruby用"地球停轉垃圾回收大法"。以後Ruby輪詢全部指針,變量和代碼產生別的引用對象和其餘值。同時Ruby經過自身的虛擬機便利內部指針。標記出這些指針引用的每一個對象。我在圖中使用M表示。
上圖中那三個被標M的對象是程序還在使用的。在內部,Ruby實際上使用一串位值,被稱爲:可用位圖(譯註:還記得《編程珠璣》裏的爲突發排序嗎,這對離散度不高的有限整數集合具備很強的壓縮效果,用以節約機器的資源。),來跟蹤對象是否被標記了。
Ruby將這個可用位圖存放在獨立的內存區域中,以便充分利用Unix的寫時拷貝化。有關此事的更多內容請關注我另外一博文《Why You Should Be Excited About Garbage Collection in Ruby 2.0》
若是說被標記的對象是存活的,剩下的未被標記的對象只能是垃圾,這意味着咱們的代碼再也不會使用它了。我會在下圖中用白格子表示垃圾對象:
接下來Ruby清除這些無用的垃圾對象,把它們送回到可用列表中:
在內部這一切發生得迅雷不及掩耳,由於Ruby實際上不會吧對象從這拷貝到那。而是經過調整內部指針,將其指向一個新鏈表的方式,來將垃圾對象歸位到可用列表中的。
如今等到下回再建立對象的時候Ruby又能夠把這些垃圾對象分給咱們使用了。在Ruby裏,對象們六道輪迴,轉世投胎,享受屢次人生。
乍一看,Python的GC算法貌似遠勝於Ruby的:寧舍潔宇而居穢室乎?爲何Ruby寧願按期強制程序中止運行,也不使用Python的算法呢?
然而,引用計數並不像第一眼看上去那樣簡單。有許多緣由使得不準多語言不像Python這樣使用引用計數GC算法:
首先,它很差實現。Python不得不在每一個對象內部留一些空間來處理引用數。這樣付出了一小點兒空間上的代價。但更糟糕的是,每一個簡單的操做(像修改變量或引用)都會變成一個更復雜的操做,由於Python須要增長一個計數,減小另外一個,還可能釋放對象。
第二點,它相對較慢。雖然Python隨着程序執行GC很穩健(一把髒碟子放在洗碗盆裏就開始洗啦),但這並不必定更快。Python不停地更新着衆多引用數值。特別是當你再也不使用一個大數據結構的時候,好比一個包含不少元素的列表,Python可能必須一次性釋放大量對象。減小引用數就成了一項複雜的遞歸過程了。
最後,它不是總奏效的。在個人下一篇包含了我這個演講剩餘部分筆記的文章中,咱們會看到,引用計數不能處理環形數據結構--也就是含有循環引用的數據結構。
英文原文地址: Generational GC in Python and Ruby 中文原文: 對比Ruby和Python的垃圾回收(2):代式垃圾回收機制
上週,我根據以前在RuPy上作的一個名爲「Visualizing Garbage Collection in Ruby and Python.」的報告寫了這篇文章的上半部分。在上篇中,我解釋了標準Ruby(也被稱爲Matz的Ruby解釋器或是MRI)是如何使用名爲標記回收(Mark and Sweep)的垃圾回收算法,這個算法是爲1960年原版本的Lisp所開發。一樣,我也介紹了Python使用一種有53年曆史的GC算法,這種算法的思路很是不一樣,稱之爲引用計數。
事實證實,Python在引用計數以外,還用了另外一個名爲Generational Garbage Collection
的算法。這意味着Python的垃圾回收器用不一樣的方式對待新建立的以及舊有的對象。而且在即將到來的2.1版本的MRI Ruby中也首次引入了Generational Garbage Collection 的垃圾回收機制(在另兩個Ruby的實現:JRuby和Rubinius中,已經使用這種GC機制不少年了,我將在下週的RubyConf大會上將它是如何在這兩種Ruby實現中工做的)。
固然,這句話「用不一樣的方式對待新建立的以及舊有的對象」是有點模糊不清,好比如何定義新、舊對象?又好比對於Ruby和Python來講具體是如何採起不一樣的對待方式?今天,咱們就來談談這兩種語言GC機制的運行原理,回答上邊那些疑問。可是在咱們開始談論Generational GC以前,咱們先要花點時間談論下Python的引用計數算法的一個嚴重的理論問題。
經過上篇,咱們知道在Python中,每一個對象都保存了一個稱爲引用計數的整數值,來追蹤到底有多少引用指向了這個對象。不管什麼時候,若是咱們程序中的一個變量或其餘對象引用了目標對象,Python將會增長這個計數值,而當程序中止使用這個對象,則Python會減小這個計數值。一旦計數值被減到零,Python將會釋放這個對象以及回收相關內存空間。
從六十年代開始,計算機科學界就面臨了一個嚴重的理論問題,那就是針對引用計數這種算法來講,若是一個數據結構引用了它自身,即若是這個數據結構是一個循環數據結構,那麼某些引用計數值是確定沒法變成零的。爲了更好地理解這個問題,讓咱們舉個例子。下面的代碼展現了一些上週咱們所用到的節點類:
咱們有一個構造器(在Python中叫作 init ),在一個實例變量中存儲一個單獨的屬性。在類定義以後咱們建立兩個節點,ABC以及DEF,在圖中爲左邊的矩形框。兩個節點的引用計數都被初始化爲1,由於各有兩個引用指向各個節點(n1和n2)。
如今,讓咱們在節點中定義兩個附加的屬性,next以及prev:
跟Ruby不一樣的是,Python中你能夠在代碼運行的時候動態定義實例變量或對象屬性。這看起來彷佛有點像Ruby缺失了某些有趣的魔法。(聲明下我不是一個Python程序員,因此可能會存在一些命名方面的錯誤)。咱們設置 n1.next 指向 n2,同時設置 n2.prev 指回 n1。如今,咱們的兩個節點使用循環引用的方式構成了一個雙端鏈表。同時請注意到 ABC 以及 DEF 的引用計數值已經增長到了2。這裏有兩個指針指向了每一個節點:首先是 n1 以及 n2,其次就是 next 以及 prev。
如今,假定咱們的程序再也不使用這兩個節點了,咱們將 n1 和 n2 都設置爲null(Python中是None)。
好了,Python會像往常同樣將每一個節點的引用計數減小到1。
請注意在以上剛剛說到的例子中,咱們以一個不是很常見的狀況結尾:咱們有一個「孤島」或是一組未使用的、互相指向的對象,可是誰都沒有外部引用。換句話說,咱們的程序再也不使用這些節點對象了,因此咱們但願Python的垃圾回收機制可以足夠智能去釋放這些對象並回收它們佔用的內存空間。可是這不可能,由於全部的引用計數都是1而不是0。Python的引用計數算法不可以處理互相指向本身的對象。
固然,上邊舉的是一個故意設計的例子,可是你的代碼也許會在不經意間包含循環引用而且你並未意識到。事實上,當你的Python程序運行的時候它將會創建必定數量的「浮點數垃圾」,Python的GC不可以處理未使用的對象由於應用計數值不會到零。
這就是爲何Python要引入Generational GC算法的緣由!正如Ruby使用一個鏈表(free list)來持續追蹤未使用的、自由的對象同樣,Python使用一種不一樣的鏈表來持續追蹤活躍的對象。而不將其稱之爲「活躍列表」,Python的內部C代碼將其稱爲零代(Generation Zero)。每次當你建立一個對象或其餘什麼值的時候,Python會將其加入零代鏈表:
從上邊能夠看到當咱們建立ABC節點的時候,Python將其加入零代鏈表。請注意到這並非一個真正的列表,並不能直接在你的代碼中訪問,事實上這個鏈表是一個徹底內部的Python運行時。 類似的,當咱們建立DEF節點的時候,Python將其加入一樣的鏈表:
如今零代包含了兩個節點對象。(他還將包含Python建立的每一個其餘值,與一些Python本身使用的內部值。)
隨後,Python會循環遍歷零代列表上的每一個對象,檢查列表中每一個互相引用的對象,根據規則減掉其引用計數。在這個過程當中,Python會一個接一個的統計內部引用的數量以防過早地釋放對象。
爲了便於理解,來看一個例子:
從上面能夠看到 ABC 和 DEF 節點包含的引用數爲1.有三個其餘的對象同時存在於零代鏈表中,藍色的箭頭指示了有一些對象正在被零代鏈表以外的其餘對象所引用。(接下來咱們會看到,Python中同時存在另外兩個分別被稱爲一代和二代的鏈表)。這些對象有着更高的引用計數由於它們正在被其餘指針所指向着。
接下來你會看到Python的GC是如何處理零代鏈表的。
經過識別內部引用,Python可以減小許多零代鏈表對象的引用計數。在上圖的第一行中你可以看見ABC和DEF的引用計數已經變爲零了,這意味着收集器能夠釋放它們並回收內存空間了。剩下的活躍的對象則被移動到一個新的鏈表:一代鏈表。
從某種意義上說,Python的GC算法相似於Ruby所用的標記回收算法。週期性地從一個對象到另外一個對象追蹤引用以肯定對象是否仍是活躍的,正在被程序所使用的,這正相似於Ruby的標記過程。
Python何時會進行這個標記過程?隨着你的程序運行,Python解釋器保持對新建立的對象,以及由於引用計數爲零而被釋放掉的對象的追蹤。從理論上說,這兩個值應該保持一致,由於程序新建的每一個對象都應該最終被釋放掉。
固然,事實並不是如此。由於循環引用的緣由,而且由於你的程序使用了一些比其餘對象存在時間更長的對象,從而被分配對象的計數值與被釋放對象的計數值之間的差別在逐漸增加。一旦這個差別累計超過某個閾值,則Python的收集機制就啓動了,而且觸發上邊所說到的零代算法,釋放「浮動的垃圾」,而且將剩下的對象移動到一代列表。
隨着時間的推移,程序所使用的對象逐漸從零代鏈表移動到一代鏈表。而Python對於一代列表中對象的處理遵循一樣的方法,一旦被分配計數值與被釋放計數值累計到達必定閾值,Python會將剩下的活躍對象移動到二代列表。
經過這種方法,你的代碼所長期使用的對象,那些你的代碼持續訪問的活躍對象,會從零代鏈表轉移到一代再轉移到二代。經過不一樣的閾值設置,Python能夠在不一樣的時間間隔處理這些對象。Python處理零代最爲頻繁,其次是一代而後纔是二代。
來看看代垃圾回收算法的核心行爲:垃圾回收器會更頻繁的處理新對象。一個新的對象便是你的程序剛剛建立的,而一個老的對象則是通過了幾個時間週期以後仍然存在的對象。Python會在當一個對象從零代移動到一代,或是從一代移動到二代的過程當中提高(promote)這個對象。
爲何要這麼作?這種算法的根源來自於弱代假說(weak generational hypothesis)。這個假說由兩個觀點構成:首先是年親的對象一般死得也快,而老對象則頗有可能存活更長的時間。
假定如今我用Python或是Ruby建立一個新對象:
根據假說,個人代碼極可能僅僅會使用ABC很短的時間。這個對象也許僅僅只是一個方法中的中間結果,而且隨着方法的返回這個對象就將變成垃圾了。大部分的新對象都是如此般地很快變成垃圾。然而,偶爾程序會建立一些很重要的,存活時間比較長的對象-例如web應用中的session變量或是配置項。
經過頻繁的處理零代鏈表中的新對象,Python的垃圾收集器將把時間花在更有意義的地方:它處理那些很快就可能變成垃圾的新對象。同時只在不多的時候,當知足閾值的條件,收集器纔回去處理那些老變量。
即將到來的Ruby 2.1版本將會首次使用基於代的垃圾回收算法!(請注意的是,其餘的Ruby實現,例如JRuby和Rubinius已經使用這個算法許多年了)。讓咱們回到上篇博文中提到的自由鏈的圖來看看它究竟是怎麼工做的。
請回憶當自由鏈使用完以後,Ruby會標記你的程序仍然在使用的對象。
從這張圖上咱們能夠看到有三個活躍的對象,由於指針n一、n二、n3仍然指向着它們。剩下的用白色矩形表示的對象便是垃圾。(固然,實際狀況會複雜得多,自由鏈可能會包含上千個對象,而且有複雜的引用指向關係,這裏的簡圖只是幫助咱們瞭解Ruby的GC機制背後的簡單原理,而不會將咱們陷入細節之中)
一樣,咱們說過Ruby會將垃圾對象移動回自由鏈中,這樣的話它們就能在程序申請新對象的時候被循環使用了。
從2.1版本開始,Ruby的GC代碼增長了一些附加步驟:它將留下來的活躍對象晉升(promote)到成熟代(mature generation)中。(在MRI的C源碼中使用了old這個詞而不是mature),接下來的圖展現了兩個Ruby2.1對象代的概念圖:
在左邊是一個跟自由鏈不相同的場景,咱們能夠看到垃圾對象是用白色表示的,剩下的是灰色的活躍對象。灰色的對象剛剛被標記。
一旦「標記清除」過程結束,Ruby2.1將剩下的標記對象移動到成熟區:
跟Python中使用三代來劃分不一樣,Ruby2.1只用了兩代,左邊是年輕的新一代對象,而右邊是成熟代的老對象。一旦Ruby2.1標記了對象一次,它就會被認爲是成熟的。Ruby會打賭剩下的活躍對象在至關長的一段時間內不會很快變成垃圾對象。
重要提醒:Ruby2.1並不會真的在內存中拷貝對象,這些表明不一樣代的區域並非由不一樣的物理內存區域構成。(有一些別的編程語言的GC實現或是Ruby的其餘實現,可能會在對象晉升的時候採起拷貝的操做)。Ruby2.1的內部實現不會將在標記&清除過程當中預先標記的對象包含在內。一旦一個對象已經被標記過一次了,那麼那將不會被包含在接下來的標記清除過程當中。
如今,假定你的Ruby程序接着運行着,創造了更多新的,更年輕的對象。則GC的過程將會在新的一代中出現,如圖:
如同Python那樣,Ruby的垃圾收集器將大部分精力都放在新一代的對象之上。它僅僅會將自上一次GC過程發生後建立的新的、年輕的對象包含在接下來的標記清除過程當中。這是由於不少新對象極可能立刻就會變成垃圾(白色標記)。Ruby不會重複標記右邊的成熟對象。由於他們已經在一次GC過程當中存活下來了,在至關長的一段時間內不會很快變成垃圾。由於只須要標記新對象,因此Ruby 的GC可以運行得更快。它徹底跳過了成熟對象,減小了代碼等待GC完成的時間。
偶然的Ruby會運行一次「全局回收」,重標記(re-marking)並重清除(re-sweeping),此次包括全部的成熟對象。Ruby經過監控成熟對象的數目來肯定什麼時候運行全局回收。當成熟對象的數目雙倍於上次全局回收的數目時,Ruby會清理全部的標記而且將全部的對象都視爲新對象。
這個算法的一個重要挑戰是值得深刻解釋的:假定你的代碼建立了一個新的年輕的對象,而且將其做爲一個已存在的成熟對象的子嗣加入。舉個例子,這種狀況將會發生在,當你往一個已經存在了很長時間的數組中增長了一個新值的時候:
讓咱們來看看圖,左邊的是新對象,而成熟的對象在右邊。在左邊標記過程已經識別出了5個新的對象目前仍然是活躍的(灰色)。但有兩個對象已經變成垃圾了(白色)。可是如何處理正中間這個新建對象?這是剛剛那個問題提到的對象,它是垃圾仍是活躍對象呢?
固然它是活躍對象了,由於有一個從右邊成熟對象的引用指向它。可是咱們前面說過已經被標記的成熟對象是不會被包含在標記清除過程當中的(一直到全局回收)。這意味着相似這種的新建對象會被錯誤的認爲是垃圾而被釋放,從而形成數據丟失。
Ruby2.1 經過監視成熟對象,觀察你的代碼是否會添加一個從它們到新建對象的引用來克服這個問題。Ruby2.1 使用了一個名爲白障(white barriers)的老式GC技術來監視成熟對象的變化 – 不管任意時刻當你添加了從一個對象指向另外一個對象的引用時(不管是新建或是修改一個對象),白障就會被觸發。白障將會檢測是否源對象是一個成熟對象,若是是的話則將這個成熟對象添加到一個特殊的列表中。隨後,Ruby2.1會將這些知足條件的成熟對象包括到下一次標記清除的範圍內,以防止新建對象被錯誤的標記爲垃圾而清除。
Ruby2.1 的白障實現至關複雜,主要是由於已有的C擴展並未包含這部分功能。Koichi Sasada以及Ruby的核心團隊使用了一個比較巧妙的方案來解決這個問題。若是想了解更多的內容,請閱讀這些相關材料:Koichi在EuRuKo 2013上的演講Koichi’s fascinating presentation。
乍眼一看,Ruby和Python的GC實現是大相徑庭的,Ruby使用John-MaCarthy的原生「標記並清除」算法,而Python使用引用計數。可是仔細看來,能夠發現Python使用了些許標記清除的思想來處理循環引用,而二者同時以類似的方式使用基於代的垃圾回收算法。Python劃分了三代,而Ruby只有兩代。
這種類似性應該不會讓人感到意外。兩種編程語言都使用了幾十年前的計算機科學研究成果來進行設計,這些成果早在語言成型以前就已經被作出來了。我比較驚異的是當你掀開不一樣編程語言的表面而深刻底層,你總可以發現一些類似的基礎理念和算法。現代編程語言應該感激那些六七十年代由麥卡錫等計算機先賢所做出的計算機科學開創性研究。
Python中的垃圾回收是以引用計數爲主,分代收集爲輔。
對象被建立,例如a=23
對象被引用,例如b=a
對象被做爲參數,傳入到一個函數中,例如func(a)
對象做爲一個元素,存儲在容器中,例如list1=[a,a]
對象的別名被顯式銷燬,例如del a
對象的別名被賦予新的對象,例如a=24
一個對象離開它的做用域,例如f函數執行完畢時,func函數中的局部變量(全局變量不會)
對象所在的容器被銷燬,或從容器中刪除對象
能夠查看a對象的引用計數,可是比正常計數大1,由於調用函數的時候傳入a,這會讓a的引用計數+1
引用計數的缺陷是循環引用的問題
執行f2(),進程佔用的內存會不斷增大。
c1.t=c2
和c2.t=c1
後,這兩塊內存的引用計數變成2.
python2運行結果:
gc模塊提供一個接口給開發者設置垃圾回收的選項
。上面說到,採用引用計數的方法管理內存的一個缺陷是循環引用,而gc模塊的一個主要功能就是解決循環引用的問題。
一、gc.set_debug(flags) 設置gc的debug日誌,通常設置爲gc.DEBUG_LEAK
二、gc.collect([generation]) 顯式進行垃圾回收,能夠輸入參數,0表明只檢查第一代的對象,1表明檢查一,二代的對象,2表明檢查一,二,三代的對象,若是不傳參數,執行一個full collection,也就是等於傳2。 返回不可達(unreachable objects)對象的數目
三、gc.get_threshold() 獲取的gc模塊中自動執行垃圾回收的頻率。
四、gc.set_threshold(threshold0[, threshold1[, threshold2]) 設置自動執行垃圾回收的頻率。
五、gc.get_count() 獲取當前自動執行垃圾回收的計數器,返回一個長度爲3的列表
必需要import gc模塊,而且is_enable()=True
纔會啓動自動垃圾回收。
這個機制的主要做用就是發現並處理不可達的垃圾對象
。
在Python中,採用分代收集的方法。把對象分爲三代,一開始,對象在建立的時候,放在一代中,若是在一次一代的垃圾檢查中,該對象存活下來,就會被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會被放到三代中。
gc模塊裏面會有一個長度爲3的列表的計數器,能夠經過gc.get_count()獲取。
例如(488,3,0),其中488是指距離上一次一代垃圾檢查,Python分配內存的數目減去釋放內存的數目,注意是內存分配,而不是引用計數的增長。例如:
3是指距離上一次二代垃圾檢查,一代垃圾檢查的次數,同理,0是指距離上一次三代垃圾檢查,二代垃圾檢查的次數。
gc模快有一個自動垃圾回收的閥值
,即經過gc.get_threshold函數獲取到的長度爲3的元組,例如(700,10,10) 每一次計數器的增長,gc模塊就會檢查增長後的計數是否達到閥值的數目,若是是,就會執行對應的代數的垃圾檢查,而後重置計數器
例如,假設閥值是(700,10,10):
gc模塊惟一處理不了的是循環引用的類都有del方法,因此項目中要避免定義del方法
運行結果:
若是把del打開,運行結果爲: