Python 中一切皆對象,對象又能夠分爲可變對象和不可變對象。兩者能夠經過原地修改,若是修改後地址不變,則是可變對象,不然爲不可變對象,地址信息能夠經過id()進行查看。git
>>> a = 10
>>> id(a)
4339392960
>>> a =11
>>> id(a)
4339392992
>>> a = [1, 2]
>>> id(a)
4342877192
>>> a.append(3)
>>> a
[1, 2, 3]
>>> id(a)
4342877192
複製代碼
Python 有內存池機制,Pymalloc機制,用於對內存的申請和釋放管理。先來看一下爲何有內存池:面試
當建立大量消耗小內存的對象時,c中頻繁調用new/malloc會致使大量的內存碎片,導致效率下降。算法
內存池的概念就是預先在內存中申請必定數量的,大小相等的內存塊留做備用,當有新的內存需求時,就先從內存池中分配內存給這個需求,不夠了以後再申請新的內存。這樣作最顯著的優點就是可以減小內存碎片,提高效率。bash
查看源碼,能夠看到 Pymalloc 對於小的對象,Pymalloc會在內存池中申請空間,通常是少於256kb,若是是大的對象,則直接調用 new/malloc 來申請新的內存空間。app
有了內存的建立,那就須要回收,垃圾回收機制,也是 Python 面試當中必問的一個知識點,接下來看看垃圾回收機制是什麼?post
垃圾回收機制,Python 採用 GC 做爲自動內存管理機制,GC要作的有2件事,一是找到內存中無用的垃圾對象資源,二是清除找到的這些垃圾對象,釋放內存給其餘對象使用。spa
如何實現上述2點了,Python 採用了 引用計數 爲主, 標誌清除和分代回收 爲輔助策略。指針
查看源碼,每個對象,在源碼裏就是一個結構體表示,都會有一個計數字段.code
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
複製代碼
PyObject是每一個對象必有的內容,其中ob_refcnt就是作爲引用計數。當一個對象有新的引用時,它的ob_refcnt就會增長,當引用它的對象被刪除,它的ob_refcnt就會減小。 一旦對象的引用計數爲0,該對象當即被回收,對象佔用的內存空間將被釋放。cdn
此算法的優勢和缺點都是很是明顯的:
優勢:
缺點:
接下來講一下什麼是循環引用:
A和B相互引用並且沒有外部引用A與B中的任何一個。也就是對象之間互相應用,致使引用鏈造成一個環。
>>>>>>a = { } #對象A的引用計數爲 1
>>>b = { } #對象B的引用計數爲 1
>>>a['b'] = b #B的引用計數增1
>>>b['a'] = a #A的引用計數增1
>>>del a #A的引用減 1,最後A對象的引用爲 1
>>>del b #B的引用減 1, 最後B對象的引用爲 1
複製代碼
執行 del 後,A、B對象已經沒有任何引用指向這兩個對象,可是這兩個對象各包含一個對方對象的引用,雖然最後兩個對象都沒法經過其它變量來引用這兩個對象了,這對GC來講就是兩個非活動對象或者說是垃圾對象。理論上是須要被回收的。 按上面的引用計數原理,要計數爲0纔會回收,可是他們的引用計數並無減小到零。所以若是是使用引用計數法來管理這兩對象的話,他們並不會被回收,它會一直駐留在內存中,就會形成了內存泄漏(內存空間在使用完畢後未釋放)。
爲了解決對象的循環引用問題,Python 引入了標記清除和分代回收兩種GC機制。
標記清除主要是解決循環引用問題。
標記清除算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收算法。 它分爲兩個階段:第一階段是標記階段,GC會把全部的 活動對象 打上標記,第二階段是把那些沒有標記的對象 非活動對象 進行回收。那麼GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?
對象之間經過引用(指針)連在一塊兒,構成一個有向圖,對象構成這個有向圖的節點,而引用關係構成這個有向圖的邊。從根對象(root object)出發,沿着有向邊遍歷對象,可達的(reachable)對象標記爲活動對象,不可達的對象就是要被清除的非活動對象。根對象就是全局變量、調用棧、寄存器。
在上圖中,咱們把小黑圈視爲全局變量,也就是把它做爲root object,從小黑圈出發,對象1可直達,那麼它將被標記,對象二、3可間接到達也會被標記,而4和5不可達,那麼一、二、3就是活動對象,4和5是非活動對象會被GC回收。
標記清除算法做爲 Python 的輔助垃圾收集技術主要處理的是容器對象(container,上面講迭代器有提到概念,能夠點擊此連接查看迭代器章節,好比list、dict、tuple等,由於對於字符串、數值對象是不可能形成循環引用問題。Python使用一個雙向鏈表將這些容器對象組織起來。
Python 這種簡單粗暴的標記清除算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描全部對象。
分代回收是一種以空間換時間的操做方式。
Python將內存根據對象的存活時間劃分爲不一樣的集合,每一個集合稱爲一個代,Python將內存分爲了3「代」,分別爲年輕代(第0代)、中年代(第1代)、老年代(第2代),他們對應的是3個鏈表,它們的垃圾收集頻率與對象的存活時間的增大而減少。新建立的對象都會分配在年輕代,年輕代鏈表的總數達到上限時,Python垃圾收集機制就會被觸發,把那些能夠被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推,老年代中的對象是存活時間最久的對象,甚至是存活於整個系統的生命週期內。同時,分代回收是創建在標記清除技術基礎之上。
分代回收一樣做爲Python的輔助垃圾收集技術處理容器對象。
『剖析Python面試知識點』完整內容請查看 : gitbook.cn/gitchat/act…
更多精彩文章請關注公衆號: 『天澄技術雜談』