筆記 python 記憶體管理

2022-07-30 01:33:09 字數 3316 閱讀 6342

筆記-python-記憶體管理

a = 1

1是乙個物件,a是引用,指向1。

>>> id(a)

這個數字代表記憶體位址;

在python中,整數和短小的字元,python都會快取這些物件,以便重複使用。當我們建立多個等於1的引用時,實際上是讓所有這些引用指向同乙個物件。

下面進行測試:

a = 1

b = 1

print(id(a), id(b), a is b)

1951821280 1951821280 true

這點需要特別注意,特別是在多重陣列建立時;

在python中,每個物件都有存有指向該物件的引用總數,即引用計數(reference count)

可以使用sys包中的getrefcount(),來檢視某個物件的引用計數。需要注意的是,當使用某個作為引數,傳遞給getrefcount()時,引數實際上建立了乙個臨時的引用;所以,getrefcount()所得到的結果,會比期望的多1.

from sys import getrefcount

a = [1, 2, 3]

print(getrefcount(a))

b = a

print(getrefcount(b))

輸出是2和3,而非1和2

python的乙個容器物件(container),比如表、詞典等,可以包含多個物件,實際上,容器物件中包含的並不是元素物件本身,而是指向各個元素物件的引用。

class from_obj(object):

def __init__(self, to_obj):

self.to_obj = to_obj

b = [1,2,3]

a = from_obj(b)

print(id(a.to_obj))

print(id(b))

結果是顯然,a引用了b。

當乙個物件a被另乙個物件b引用時,a的引用計數將增加1.

容器的引用可能構成複雜的拓撲。

引用環:reference cycle兩個物件可能相互引用,構成引用環。

a =

b = [a]

即使是乙個物件,只需要自己引用自己,也能構成引用環。

a =

print(getrefcount(a))

引用環會給垃圾**機制帶來很大的麻煩。

當然,引用計數是會減少的,比如,刪除引用;

from sys import getrefcount

a = [1, 2, 3]

b = a

print(getrefcount(b))

del a

print(getrefcount(b))

或者,引用指向其它物件;

from sys import getrefcount

a = [1, 2, 3]

b = a

print(getrefcount(b))

a = 1

print(getrefcount(b))

記憶體不是無限的,很多物件也不會一直占用記憶體,因此,在合適的時候需要記憶體**garbage colleciton;

python中的垃圾**是以引用計數為主,分代收集為輔。引用計數的缺陷是迴圈引用的問題。

在python中,如果乙個物件的引用數為0,python虛擬機器就會**這個物件的記憶體。

**垃圾無疑需要占用處理能力,垃圾**時,python不能進行其它的任務。頻繁的垃圾**將大大降低python的工作效率,如果記憶體中的物件不多,就沒有必要總啟動垃圾**。

所以,python只會在特定條件下,自動啟動垃圾**。當python執行時,會記錄其中分配物件(object allocation)和取消分配物件(object deallocation)的次數。當兩者的差值高於某個閾值時,垃圾**才會啟動。

可以通過gc模組的get_threshold()方法,檢視該閾值:

import gc

print(gc.get_threshold())

返回(700, 10, 10),後面的兩個10是與分代**相關的閾值,後面可以看到。700即是垃圾**啟動的閾值。可以通過gc中的set_threshold()方法重新設定。

也可以手動啟動垃圾**,即使用gc.collect()。

導致引用計數+1的情況

物件被建立,例如a=23

物件被引用,例如b=a

物件被作為引數,傳入到乙個函式中,例如func(a) 物件作為引數傳入到乙個函式中會 +2

物件作為乙個元素,儲存在容器中,例如list1=[a,a]

導致引用計數-1的情況

物件的別名被顯式銷毀,例如del a

物件的別名被賦予新的物件,例如a=24

乙個物件離開它的作用域,例如f函式執行完畢時,func函式中的區域性變數(全域性變數不會)

物件所在的容器被銷毀,或從容器中刪除物件

python同時採用了分代(generation)**的策略。這一策略的基本假設是,存活時間越久的物件,越不可能在後面的程式中變成垃圾。程式往往會產生大量的物件,許多物件很快產生和消失,但也有一些物件長期被使用。出於信任和效率,對於這樣一些「長壽」物件,我們相信它們的用處,所以減少在垃圾**中掃瞄它們的頻率。

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]

del a

del b

從需求來看,上面兩個物件不再使用,應該釋放對應的資源;但由於引用環,這兩個物件的引用計數都沒有降到0,不會被**;

為了**這樣的引用環,python複製每個物件的引用計數,可以記為gc_ref。假設,每個物件i,該計數為gc_ref_i。python會遍歷所有的物件i。對於每個物件i引用的物件j,將相應的gc_ref_j減1。

在結束遍歷後,gc_ref不為0的物件,和這些物件引用的物件,以及繼續更下游引用的物件,需要被保留。而其它的物件則被垃圾**。

記憶體管理筆記

mrc手動記憶體管理 1.系統不會去檢查已釋放的物件,也就是說,當乙個物件的引用計數為0時,這個物件此時再呼叫其方法不會報錯,成為野指標 除非開啟殭屍除錯診斷,一旦開啟殭屍診斷就會發現崩潰。2.當把這個物件賦值為nil時,成為空指標 再呼叫其方法,將不會出錯,更不會崩潰。3.dealloc方法必須含...

python 記憶體分析 python記憶體管理分析

記憶體管理,對於python這樣的動態語言,是至關重要的一部分,它在很大程度上甚至決定了python的執行效率,因為在python的執行中,會建立和銷毀大量的物件,這些都涉及到記憶體的管理。小塊空間的記憶體池 在python中,許多時候申請的記憶體都是小塊的記憶體,這些小塊記憶體在申請後,很快又會被...

python 記憶體管理

記憶體管理,對於python這樣的動態語言,是至關重要的一部分,它在很大程度上甚至決定了python的執行效率,因為在python的執行中,會建立和銷毀大量的物件,這些都涉及到記憶體的管理。小塊空間的記憶體池 在python中,許多時候申請的記憶體都是小塊的記憶體,這些小塊記憶體在申請後,很快又會被...