先從較淺的層面來講,Python的內存管理機制能夠從三個方面來說html
(1)引用計數app
(2)垃圾回收函數
(3)內存池機制性能
在Python中,每一個對象都有存有指向該對象的引用總數,即引用計數(reference count)。spa
咱們可使用sys包中的getrefcount(),來查看某個對象的引用計數。須要注意的是,當使用某個引用做爲參數,傳遞給getrefcount()時,參數實際上建立了一個臨時的引用。所以,getrefcount()所獲得的結果,會比指望的多1。操作系統
from sys import getrefcount # 普通引用 a = [1, 2, 3] print(getrefcount(a)) # 2 b = a print(getrefcount(b)) # 3 # 對象間引用 # class class from_obj(object): def __init__(self, to_obj): self.to_obj = to_obj c = [1,2,3] d = from_obj(c) print(id(d.to_obj)) # 86378576 print(id(c)) # 86378576 print(getrefcount(c)) # 3 print(getrefcount(d)) # 2 # 其餘 e = [] f = [e,e] print(getrefcount(e)) # 4 print(getrefcount(f)) # 2
####### 引用減小 from sys import getrefcount a = [1, 2, 3] b = a print(getrefcount(b)) # 3 del a print(getrefcount(b)) # 2 c = [1,2,3] del c[0] print(c) # [2, 3] print(getrefcount(c)) # 2
########### 引用環 from sys import getrefcount a = [] b = [a] a.append(b) print(getrefcount(a)) # 3 print(getrefcount(b)) # 3 c = [] c.append(c) print(getrefcount(c)) # 3
容器對象的引用可能構成很複雜的拓撲結構。咱們能夠用objgraph包來繪製其引用關係,好比code
x = [1, 2, 3] y = [x, dict(key1=x)] z = [y, (x, y)] import objgraph objgraph.show_refs([z], filename='ref_topo.png')
從基本原理上,當Python的某個對象的引用計數降爲0時,說明沒有任何引用指向該對象,該對象就成爲要被回收的垃圾了。好比某個新建對象,它被分配給某個引用,對象的引用計數變爲1。若是引用被刪除,對象的引用計數爲0,那麼該對象就能夠被垃圾回收。好比下面的表:htm
a = [1, 2, 3] del a
del a後,已經沒有任何引用指向以前創建的[1, 2, 3]這個表。用戶不可能經過任何方式接觸或者動用這個對象。這個對象若是繼續待在內存裏,就成了不健康的脂肪。當垃圾回收啓動時,Python掃描到這個引用計數爲0的對象,就將它所佔據的內存清空。對象
垃圾回收時,Python不能進行其它的任務。頻繁的垃圾回收將大大下降Python的工做效率。若是內存中的對象很少,就沒有必要總啓動垃圾回收。因此,Python只會在特定條件下,自動啓動垃圾回收。當Python運行時,會記錄其中分配對象(object allocation)和取消分配對象(object deallocation)的次數。當二者的差值高於某個閾值時,垃圾回收纔會啓動。blog
咱們能夠經過gc模塊的get_threshold()方法,查看該閾值:
import gc print(gc.get_threshold())
返回(700, 10, 10),後面的兩個10是與分代回收相關的閾值,後面能夠看到。700便是垃圾回收啓動的閾值。能夠經過gc中的set_threshold()方法從新設置。
咱們也能夠手動啓動垃圾回收,即便用gc.collect()。
Python將全部的對象分爲0,1,2三代。全部的新建對象都是0代對象。當某一代對象經歷過垃圾回收,依然存活,那麼它就被納入下一代對象。垃圾回收啓動時,必定會掃描全部的0代對象。若是0代通過必定次數垃圾回收,那麼就啓動對0代和1代的掃描清理。當1代也經歷了必定次數的垃圾回收後,那麼會啓動對0,1,2,即對全部對象進行掃描。
這兩個次數即上面get_threshold()返回的(700, 10, 10)返回的兩個10。也就是說,每10次0代垃圾回收,會配合1次1代的垃圾回收;而每10次1代的垃圾回收,纔會有1次的2代垃圾回收。
一樣能夠用set_threshold()來調整,好比對2代對象進行更頻繁的掃描。
import gc gc.set_threshold(700, 10, 5)
引用環的存在會給上面的垃圾回收機制帶來很大的困難。這些引用環可能構成沒法使用,但引用計數不爲0的一些對象。
a = [] b = [a] a.append(b) del a del b
上面咱們先建立了兩個表對象,並引用對方,構成一個引用環。刪除了a,b引用以後,這兩個對象不可能再從程序中調用,就沒有什麼用處了。可是因爲引用環的存在,這兩個對象的引用計數都沒有降到0,不會被垃圾回收。
孤立的引用環
爲了回收這樣的引用環,Python複製每一個對象的引用計數,能夠記爲gc_ref。假設,每一個對象i,該計數爲gc_ref_i。Python會遍歷全部的對象i。對於每一個對象i引用的對象j,將相應的gc_ref_j減1。
遍歷後的結果
在結束遍歷後,gc_ref不爲0的對象,和這些對象引用的對象,以及繼續更下游引用的對象,須要被保留。而其它的對象則被垃圾回收。
Python的內存機制以金字塔行,-1,-2層主要有操做系統進行操做,
第0層是C中的malloc,free等內存分配和釋放函數進行操做;
第1層和第2層是內存池,有Python的接口函數PyMem_Malloc函數實現,當對象小於256K時有該層直接分配內存;
第3層是最上層,也就是咱們對Python對象的直接操做;
在 C 中若是頻繁的調用 malloc 與 free 時,是會產生性能問題的.再加上頻繁的分配與釋放小塊的內存會產生內存碎片. Python 在這裏主要乾的工做有:
若是請求分配的內存在1~256字節之間就使用本身的內存管理系統,不然直接使用 malloc.
這裏仍是會調用 malloc 分配內存,但每次會分配一塊大小爲256k的大塊內存.
經由內存池登記的內存到最後仍是會回收到內存池,並不會調用 C 的 free 釋放掉.以便下次使用.對於簡單的Python對象,例如數值、字符串,元組(tuple不容許被更改)採用的是複製的方式(深拷貝?),也就是說當將另外一個變量B賦值給變量A時,雖然A和B的內存空間仍然相同,但當A的值發生變化時,會從新給A分配空間,A和B的地址變得再也不相同。
https://www.cnblogs.com/vamei/p/3232088.html
https://www.cnblogs.com/CBDoctor/p/3781078.html