python內存管理

內存管理

先從較淺的層面來講,Python的內存管理機制能夠從三個方面來說html

(1)引用計數app

(2)垃圾回收函數

(3)內存池機制性能

 

(1)引用計數

在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包來繪製其引用關係

容器對象的引用可能構成很複雜的拓撲結構。咱們能夠用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')

 

(2)垃圾回收

引用計數爲0則回收

從基本原理上,當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的對象,和這些對象引用的對象,以及繼續更下游引用的對象,須要被保留。而其它的對象則被垃圾回收。

 

(3)內存池機制

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的地址變得再也不相同。

 

 

參考or轉發

https://www.cnblogs.com/vamei/p/3232088.html

https://www.cnblogs.com/CBDoctor/p/3781078.html

相關文章
相關標籤/搜索