04.記憶體管理機制
記憶體管理機制
1.垃圾回收機制GC
1.1、什麼是垃圾回收機制?
垃圾回收機制(簡稱GC)是Python直譯器自帶一種機,專門用來回收不可用的變數值所佔用的記憶體空間
1.2、為什麼要用垃圾回收機制?
程式執行過程中會申請大量的記憶體空間,而對於一些無用的記憶體空間如果不及時清理的話會導致記憶體使用殆盡(記憶體溢位),導致程式崩潰,因此管理記憶體是一件重要且繁雜的事情,而python直譯器自帶的垃圾回收機制把程式設計師從繁雜的記憶體管理中解放出來。
1.3、GC原理
1.3.1、堆區與棧區
在定義變數時,變數名與變數值都是需要儲存的,分別對應記憶體中的兩塊區域:堆區與棧區。
1、變數名與值記憶體地址的關聯關係存放於棧區
2、變數值存放於堆區,記憶體管理回收的則是堆區的內容
1.3.2 直接引用與間接引用
直接引用指的是從棧區出發直接引用到的記憶體地址。
間接引用指的是從棧區出發引用到堆區後,再通過進一步引用才能到達的記憶體地址。
l2 = [20, 30] # 列表本身被變數名l2直接引用,包含的元素被列表間接引用 x = 10 # 值10被變數名x直接引用 l1 = [x, l2] # 列表本身被變數名l1直接引用,包含的元素被列表間接引用
引用計數
引用計數增加
x = 18 # 值18的引用計數為1
y = x # 值18的引用計數為2
l = [111, x] # 值18的引用計數為3
引用計數減少
del x # 值18的引用計數為2
y = 100 # 值18的引用計數為1
del l[1] # 值18的應用計數為0
引用計數機制存在著一個致命的弱點,即迴圈引用(也稱交叉引用)
l1 = [111] l2 = [222] l1.append(l2) l2.append(l1) print(l1) # l1 = [111的記憶體地址,l2的記憶體地址] print(l2) # l2 = [222的記憶體地址,l1的記憶體地址]
由於相互引用的存在,每一個物件的引用計數都不為0,因此這些物件所佔用的記憶體永遠不會被釋放,所以迴圈引用是致命的,這與手動進行記憶體管理所產生的記憶體洩露毫無區別。 所以Python引入了“標記-清除” 與“分代回收”來分別解決引用計數的迴圈引用與效率低的問題
分代回收
分代回收的核心思想是:在歷經多次掃描的情況下,都沒有被回收的變數,gc機制就會認為,該變數是常用變數,gc對其掃描的頻率會降低
分代
指的是根據存活時間來為變數劃分不同等級(也就是不同的代)
新定義的變數,放到新生代這個等級中,假設每隔1分鐘掃描新生代一次,如果發現變數依然被引用,那麼該物件的權重(權重本質就是個整數)加一,當變數的權重大於某個設定得值(假設為3),會將它移動到更高一級的青春代,青春代的gc掃描的頻率低於新生代(掃描時間間隔更長),假設5分鐘掃描青春代一次,這樣每次gc需要掃描的變數的總個數就變少了,節省了掃描的總時間,接下來,青春代中的物件,也會以同樣的方式被移動到老年代中。也就是等級(代)越高,被垃圾回收機制掃描的頻率越低
回收:
回收依然是使用引用計數作為回收的依據
標記/清除
標記/清除演算法的做法是當應用程式可用的記憶體空間被耗盡的時,就會停止整個程式,然後進行兩項工作,第一項則是標記,第二項則是清除
1、標記
通俗地講就是:
棧區相當於“根”,凡是從根出發可以訪達(直接或間接引用)的,都稱之為“有根之人”,有根之人當活,無根之人當死。
具體地:標記的過程其實就是,遍歷所有的GC Roots物件(棧區中的所有內容或者執行緒都可以作為GC Roots物件),然後將所有GC Roots的物件可以直接或間接訪問到的物件標記為存活的物件,其餘的均為非存活物件,應該被清除。
2、清除
清除的過程將遍歷堆中所有的物件,將沒有標記的物件全部清除掉。
小整數池
整數在程式中的使用非常廣泛,Python為了優化速度,使用了小整數物件池, 避免為整數頻繁申請和銷燬記憶體空間。
Python 對小整數的定義是 [-5, 256] 這些整數物件是提前建立好的,不會被垃圾回收。