Python入門【內存管理機制、Python緩存機制、垃圾回收機制、分代回收機制】(三十二)

👏作者簡介:大家好,我是愛敲代碼的小王,CSDN博客博主,Python小白
📕系列專欄:python入門到實戰、Python爬蟲開發、Python辦公自動化、Python數據分析、Python前后端開發
📧如果文章知識點有錯誤的地方,請指正!和大家一起學習,一起進步👀
🔥如果感覺博主的文章還不錯的話,請👍三連支持👍一下博主哦
🍂博主正在努力完成2023計劃中:以夢為馬,揚帆起航,2023追夢人

🔥🔥🔥 python入門到實戰專欄:從入門到實戰?

🔥🔥🔥 Python爬蟲開發專欄:從入門到實戰

🔥🔥🔥?Python辦公自動化專欄:從入門到實戰

🔥🔥🔥 Python數據分析專欄:從入門到實戰

🔥🔥🔥 Python前后端開發專欄:從入門到實戰

目錄

內存管理機制

Python緩存機制

垃圾回收機制

分代回收機制


內存管理機制

Python是由C語言開發的,底層操作都是基于C語言實現,Python中創建每個對象,內部都會與C語言結構體維護一些值。?

源碼下載,https://www.python.org/

將壓縮文件減壓,可以看到有很多文件,主要關心兩個(Include、 Objects) 在Include目錄下object.h中可以查看創建對象的結構體。?

#define _PyObject_HEAD_EXTRA           \struct _object *_ob_next;           \struct _object *_ob_prev;
typedef struct _object {_PyObject_HEAD_EXTRAPy_ssize_t ob_refcnt;PyTypeObject *ob_type;
} PyObject;
typedef struct {PyObject ob_base;Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

在創建對象時,每個對象至少內部4個值,PyObject結構體(上一個 對象、下一個對象、類型、引用個數)。

有多個元素組成的對象使用PyVarObject,里面由:PyObject結構體(上一個對象、下一個對象、類型、引用個數)+Ob_size(items=元素,元素個數)。

環狀雙向鏈表refchain

在python程序中創建的任何對象都會被放在refchain鏈表中。

類型封裝結構體

f = 3.14
'''
內部會創建:
1.開辟內存
2.初始化ob_fval = 3.14 值ob_type = float 類型ob_refcnt = 1 引用數量
3.將對象加入到雙向鏈表refchain中_ob_next = refchain中的上一個對象_ob_prev = refchain中的下一個對象
'''

Python緩存機制

小整數對象池?

一些整數在程序中的使用非常廣泛,Python為了優化速度,使用了小整數對象池, 避免為整數頻繁申請和銷毀內存空間。

Python 對小整數的定義是 [-5, 257) 這些整數對象是提前建立好的,不會被垃圾回收。在一個 Python 的程序中,所有位于這個范圍內的整數使用的都是同一個對象。在一個 Python 的程序中,無論這個整數處于LEGB(局部變量,閉包,全局,內建模塊)中的哪個位置, 所有位于這個范圍內的整數使用的都是同一個對象。

【示例】驗證小整數池

In [8]: a = 100In [9]: id(a)
Out[9]: 140705185112832#刪除變量a
In [10]: del aIn [11]: b = 100In [12]: id(b)
Out[12]: 140705185112832

大整數對象池

每一個大整數,均創建一個新的對象。

【示例】驗證大整數池

In [13]: a = 257In [14]: b = 257In [15]: id(a)
Out[15]: 1747036560496In [16]: id(b)
Out[16]: 1747036558352In [17]: a is b
Out[17]: False

intern機制

每個單詞(字符串),不夾雜空格或者其他符號,默認開啟intern機制,共享內存,靠引用計數決定是否銷毀。

【示例】intern機制

>>> a = 'helloworld'
>>> b = 'helloworld'
>>> a is b
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b

free_list機制

當一個對象的引用計數器為0時,按理說應該回收,但內存不會直接回收,而是將對象添加到free_list鏈表中緩存。以后再去創建對象 時,不再重新開辟內存,而是直接使用free_list。

以上的free_list的代表:float,list,tuple,dict。

>>> f1 = 3.14
>>> id(f1)
2078136427568
>>> del f1
>>> f2 = 9.998
>>> id(f2)
2078136427568

f1 = 3.14,會創建float類型,并且加入refchain中。del f1 則減1, refchain移除,按理會進行銷毀,但是實際中不會真正銷毀,而是 會添加到free_list。以后創建f2=9.998等,只要是float類型,則不會重新開辟內存,會去free_list獲取對象,對象內部進行初始化, 替換值3.14換成9.99,再放到refchain中。不是所有的都放入 free_list。例如存儲100個緩存,則前面新創建的80個會放入 free_list。如果滿了81之后則會銷毀。

1、 float類型,維護的free_list鏈表最多可緩存100個float對象。

#ifndef PyFloat_MAXFREELIST//定義free_list的最大長度
#define PyFloat_MAXFREELIST 100
#endif

2、 list類型,維護的free_list數組最多可緩存80個list對象。

#ifndef PyList_MAXFREELIST#define
PyList_MAXFREELIST 80
#endif

【示例】

>>> v1 = [11,22,33]
>>> id(v1)
2078137539904
>>> del v1
>>> v2 = ['a','b']
>>> id(v2)
2078137539904

3、 tuple類型,維護一個free_list數組且數組含量20,數組中元素 可以是鏈表且每個鏈表最多可以含鈉2000個元組對象。元組的 free_list數據在存儲數據時,是根據元組可容納的個數為索引找 到free_list數組中對應的鏈表,并添加到鏈表中。

>>> t1 = (1,2,3)
>>> id(t1)
2078137564992
>>> del t1 #元組的數量是3,所以把這個對象緩存到
free_list[3]的鏈表中
>>> t2 = ('a','b','c')
#元組的數量也是3,不會重新開辟內存,而是去
free_list[3]對應的鏈表中拿到一個對象來使用
>>> id(t2)
2078137564992
>>> del t2
>>> t3 = (11,22,33,44)
>>> id(t3)
2078138219472

?4、 dict類型,維護的free_list數組最多可緩存80個dict對象。

#ifndef PyDict_MAXFREELIST#define
PyDict_MAXFREELIST 80
#endif

【示例】

>>> d1 = {'name':'zs'}
>>> id(d1)
2078137472640
>>> del d1
>>> d2 = {'age':30}
>>> id(d2)
2078137472640

垃圾回收機制

Python 內部采用 引用計數法 ,為每個對象維護引用次數,并據此回收不再需要的垃圾對象。由于引用計數法存在重大缺陷,循環引 用時有內存泄露風險,因此 Python 還采用 標記清除法 來回收存在循環引用的垃圾對象。此外,為了提高垃圾回收( GC )效率,Python 還引入了 分代回收機制 。?

?引用計數法

Python采用了類似Windows內核對象一樣的方式來對內存進行管理。每一個對象,都維護這一個對指向該對對象的引用的計數。

引用計數 是計算機編程語言中的一種 內存管理技術 ,它將資源被引用的次數保存起來,當引用次數變為 0 時就將資源釋放。它管理 的資源并不局限于內存,還可以是對象、磁盤空間等等。

Python 也使用引用計數這種方式來管理內存,每個 Python 對象都包含一個公共頭部,頭部中的 ob_refcnt 字段便用于維護對象被引用 次數。回憶對象模型部分內容,我們知道一個典型的 Python 對象結構如下:

當創建一個對象實例時,先在堆上為對象申請內存,對象的引用計數被初始化為 1 。以 Python 為例,我們創建一個 float 對象保存 6.66,并把它賦值到變量 f :?

>>> f = 6.66
>>> f
6.66

由于此時只有變量 f 引用 float 對象,因此它的引用計數為 1 :

當我們把 pi 賦值給 f 后,float 對象的引用計數就變成了 2 ,因為現在有兩個變量引用它:?

>>> ff = f
>>> ff
6.66

我們新建一個 list 對象,并把 float 對象保存在里面。這樣一來, float 對象有多了一個來自 list 對象的引用,因此它的引用計數又加 一,變成 3 了:?

>>> l = [f]
>>> l
[6.66]

標準庫 sys 模塊中有一個函數 getrefcount 可以獲取對象引用計數:

>>> import sys
>>> sys.getrefcount(pi)
4

?咦!引用計數不應該是 3 嗎?為什么會是 4 呢?由于 float 對象被作為參數傳給 getrefcount 函數,它在函數執行過程中作為函數的局部變量存在,創建了一個臨時的引用,因此又多了一個引用:

隨著 getrefcount 函數執行完畢并返回,它的棧幀對象將從調用鏈中解開并銷毀,這時 float 對象的引用計數也跟著下降。因此,當一個 對象作為參數傳個函數后,它的引用計數將加一;當函數返回,局部名字空間銷毀后,對象引用計數又減一。

引用計數就這樣隨著引用關系的變動,不斷變化著。當所有引用都消除后,引用計數就降為零,這時 Python 就可以安全地銷毀對象, 回收內存了:?

>>> del l
>>> del ff
>>> del f

引用計數增加

1、 對象被創建

2 、如果有新的對象使用該對象

3 、作為容器對象的一個元素

4、 被作為參數傳遞給函數

引用計數減少

1、 對象的引用被顯示的銷毀

2、 新對象不再使用該對象

3 、對象從列表中被移除,或者列表對象本身被銷毀

4 、函數調用結束?

引用計數機制的優點?

1、簡單

2、實時性:一旦沒有引用,內存就直接釋放了。

?引用計數機制的缺點

1、維護引用計數消耗資源

2、循環引用的問題無法解決

a = [1,2]
b = [3,4]
a.append(b) #b的計數器2
b.append(a) #a的計數器2
del a
del b

?標記-清除

引用計數法能夠解決大多數垃圾回收的問題,但是遇到兩個對象相互引用的情況,del語句可以減少引用次數,但是引用計數不會歸 0,對象也就不會被銷毀,從而造成了內存泄漏問題。針對該情況, Python引入了標記-清除機制

循環引用?

引用計數這種管理內存的方式雖然很簡單,但是有一個比較大的瑕疵,即它不能很好的解決循環引用問題。如上圖所示:對象 A 和對 象 B,相互引用了對方作為自己的成員變量,只有當自己銷毀時, 才會將成員變量的引用計數減 1。因為對象 A 的銷毀依賴于對象 B 銷毀,而對象 B 的銷毀與依賴于對象 A 的銷毀,這樣就造成了我們 稱之為循環引用(Reference Cycle)的問題,這兩個對象即使在外界已經沒有任何指針能夠訪問到它們了,它們也無法被釋放。?

class People:pass
class Cat:pass
#創建People的實例對象
p = People()
#創建Dog的實例對象
c = Cat()
#People的寵物屬性指向Cat
p.pet = c
#Cat的主人屬性指向People
c.master = p
#刪除p和c對象
del p
del c

上述實例中,對象p中的屬性引用c,而對象c中屬性同時來引用p, 從而造成僅僅刪除p和c對象,也無法釋放其內存空間,因為他們依然在被引用。深入解釋就是,循環引用后,p和c被引用個數為2, 刪除p和c對象后,兩者被引用個數變為1,并不是0,而python只有 在檢查到一個對象的被引用個數為0時,才會自動釋放其內存,所以 這里無法釋放p和c的內存空間。

主動思路一般分為兩步:垃圾識別 垃圾回收 。垃圾對象被識別出來后,回收就只是自然而然的工作了,因此垃圾識別是解決問題的關鍵。那么,有什么辦法可以將垃圾對象識別出來呢?我們來考察一個一般化例子:

這是一個對象引用關系圖,其中灰色部分是需要回收但由于循環引用而無法回收的垃圾對象,綠色部分是被程序引用而不能回收的活躍對象。如果我們能夠將活躍對象逐個遍歷并標記,那么最后沒有被標記的對象就是垃圾對象。

遍歷活躍對象,第一步需要找出 根對象 ( root object )集合。所謂根對象,就是指被全局引用或者在棧中引用的對象,這部對象是不能被刪除的。因此,我們將這部分對象標記為綠色,作為活躍對象遍歷的起點。?

根對象本身是 可達的 ( reachable ),不能刪除;被根對象引用的對象也是可達的,同樣不能刪除;以此類推。我們從一個根對象出發,沿著引用關系遍歷,遍歷到的所有對象都是可達的,不能刪除。?

?這樣一來,當我們遍歷完所有根對象,活躍對象也就全部找出來了:

而沒有被標色的對象就是 不可達 ( unreachable )的垃圾對象,可以被安全回收。循環引用的致命缺陷完美解決了!?

這就是垃圾回收中常用的 標記清除法 。?

【示例】標記清除法

?上圖中小黑點(變量)表示根節點,從根節點出發,每個對象都有引用和被引用的情況,如果該對象找不到根節點,那么就會被清除,如圖1,2,3都有被小黑點(變量)引用,4,5沒有變量引用,所以 4,5就會被清除。

分代回收機制

Python 程序啟動后,內部可能會創建大量對象。如果每次執行標記清除法時,都需要遍歷所有對象,多半會影響程序性能。為此, Python引入分代回收機制——將對象分為若干“”( generation ), 每次只處理某個代中的對象,因此 GC 卡頓時間更短。

考察對象的生命周期,可以發現一個顯著特征:一個對象存活的時間越長,它下一刻被釋放的概率就越低。我們應該也有這樣的親身體會:經常在程序中創建一些臨時對象,用完即刻釋放;而定義為 全局變量的對象則極少釋放。

因此,根據對象存活時間,對它們進行劃分就是一個不錯的選擇。 對象存活時間越長,它們被釋放的概率越低,可以適當降低回收頻率;相反,對象存活時間越短,它們被釋放的概率越高,可以適當提高回收頻率。

對象存活時間釋放概率回收頻率

Python 內部根據對象存活時間,將對象分為 3 代(見 Include/internal/mem.h ):

#define NUM_GENERATIONS 3

?隨著時間的推進,程序冗余對象逐漸增多,達到一定閾值,系統進行回收。

這 3 個代分別稱為:初生代、中生代 以及 老生代。當這 3 個代初始化完畢后,對應的 gc_generation 數組大概是這樣的:

import gc
#python 中內置模塊gc觸發
print(gc.get_threshold()) #查看gc默認值
#輸出(700, 10, 10)

第一代鏈表:

當第一代達到700,就開始檢測哪些對象引用計數變成0了,把不是0的放到第二代鏈表里,此時第一代鏈表就是空了,當再次達到700 時,就再檢測一遍。

第二代鏈表:

當第二代鏈表達到10,就檢測一次。

第三代鏈表:

第三代鏈表檢測10之后,第三代鏈表檢測一次。

import gc#返回一個元組,分別獲取這三代當前計數
gc.get_count()  #返回一個元組,分別獲取這三代當前的收集閾值
gc.get_threshold()#設置閾值
gc.set_threshold()#關閉gc垃圾回收機制
gc.disable()

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/43631.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/43631.shtml
英文地址,請注明出處:http://en.pswp.cn/news/43631.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode150道面試經典題-- 存在重復元素 II(簡單)

1.題目 給你一個整數數組 nums 和一個整數 k &#xff0c;判斷數組中是否存在兩個 不同的索引 i 和 j &#xff0c;滿足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b;否則&#xff0c;返回 false 。 2.示例 示例 1&#xff1a; 輸…

CSS中的字體屬性有哪些值,并分別描述它們的作用。

聚沙成塔每天進步一點點 ? 專欄簡介? font-style? font-weight? font-size? font-family? font-variant? line-height? letter-spacing? word-spacing? font? 寫在最后 ? 專欄簡介 前端入門之旅&#xff1a;探索Web開發的奇妙世界 記得點擊上方或者右側鏈接訂閱本專…

JS中對象數組深拷貝方法

structuredClone() JavaScript 中提供了一個原生 API 來執行對象的深拷貝&#xff1a;structuredClone。它可以通過結構化克隆算法創建一個給定值的深拷貝&#xff0c;并且還可以傳輸原始值的可轉移對象。 當對象中存在循環引用時&#xff0c;仍然可以通過 structuredClone()…

過濾字符,繞過

構造不包含字母和數字的webshell <?phpecho "A"^""; ?>運行結果為! 代碼中對字符"A"和字符”"進行了異或操作。在PHP中&#xff0c;兩個變量進行異或時&#xff0c;先會將字符串轉換成ASCII值&#xff0c;再將ASCII值轉換成二進制…

容器docker安裝及應用

目錄 二進制安裝docker應用啟動docker拉取鏡像查看當前主機鏡像查看鏡像詳細信息運行容器 二進制安裝docker 環境 centos 7 [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# wget -P /data/ https://download.docker.com/linux/static/stable/x86_64/docker-18.03.1-ce.t…

【聲波】聲波在硼酸、硫酸鎂 (MgSO4) 和純水中的吸收研究(Matlab代碼實現)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;歡迎來到本博客????&#x1f4a5;&#x1f4a5; &#x1f3c6;博主優勢&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客內容盡量做到思維縝密&#xff0c;邏輯清晰&#xff0c;為了方便讀者。 ??座右銘&a…

MAC 命令行啟動tomcat的詳細介紹

MAC 命令行啟動tomcat MAC 命令行啟動tomcat的詳細介紹 一、修改授權 進入tomcat的bin目錄,修改授權 1 2 3 ? bin pwd /Users/yp/Documents/workspace/apache-tomcat-7.0.68/bin ? bin sudo chmod 755 *.sh sudo為系統超級管理員權限.chmod 改變一個或多個文件的存取模…

2.js中attr()用來修改或者添加屬性或者屬性值

attr()可以用來修改或者添加屬性或者屬性值 例&#xff1a;<input type"button" class"btn btn-info" id"subbtn" style"font-size:12px" value"我也說一句"/>1.如果想獲取input中value的值 $(#subbtn).attr(value);…

ASP.NET Core中路由規則匹配

RESTful約束&#xff0c;如果在一個控制器里面有多個Get、Post...的操作 1、在一個控制器里面可以定義多個API方法 2、通過路由規則來區分 /// <summary> /// 獲取用戶信息 /// </summary> /// <param name"user"></param> /// <returns…

c++ | 字節轉換 | 字長 | 機器位數

為什么有的時候腦子轉不過來&#xff1f;&#xff1f; 為什么要對字節、機器長啊、位啊都要門清 位數 一般的就是指計算機的位數&#xff0c;比如64位/32位&#xff0c;更簡單的理解&#xff0c;計算機就是在不停的做二進制的計算&#xff0c;比如32位計算機&#xff0c;在長…

[保研/考研機試] KY26 10進制 VS 2進制 清華大學復試上機題 C++實現

題目鏈接&#xff1a; 10進制 VS 2進制http://www.nowcoder.com/share/jump/437195121691738172415 描述 對于一個十進制數A&#xff0c;將A轉換為二進制數&#xff0c;然后按位逆序排列&#xff0c;再轉換為十進制數B&#xff0c;我們稱B為A的二進制逆序數。 例如對于十進制…

算法基礎課——基礎算法(模板整理)

快速排序 快速排序 #include <iostream> #include <algorithm> using namespace std; int n; int s[100000]; int main() {cin>>n;for(int i0;i<n;i){cin>>s[i];}sort(s,sn);for(int i0;i<n;i){cout<<s[i]<<" ";}cout<…

4.物聯網LWIP之C/S編程

LWIP配置 服務器端實現 客戶端實現 錯誤分析 一。LWIP配置&#xff08;FREERTOS配置&#xff0c;ETH配置&#xff0c;LWIP配置&#xff09; 1.FREERTOS配置 為什么要修改定時源為Tim1&#xff1f;不用systick&#xff1f; 原因&#xff1a;HAL庫與FREERTOS都需要使用systi…

leetcode做題筆記89. 格雷編碼

n 位格雷碼序列 是一個由 2n 個整數組成的序列&#xff0c;其中&#xff1a; 每個整數都在范圍 [0, 2n - 1] 內&#xff08;含 0 和 2n - 1&#xff09;第一個整數是 0一個整數在序列中出現 不超過一次每對 相鄰 整數的二進制表示 恰好一位不同 &#xff0c;且第一個 和 最后一…

C語言好題解析(三)

目錄 選擇題一選擇題二選擇題三選擇題四編程題一編程題二 選擇題一 以下程序段的輸出結果是&#xff08;&#xff09;#include<stdio.h> int main() { char s[] "\\123456\123456\t"; printf("%d\n", strlen(s)); return 0; }A: 12 B: 13 …

Lnton羚通關于【PyTorch】教程:torchvision 目標檢測微調

torchvision 目標檢測微調 本教程將使用Penn-Fudan Database for Pedestrian Detection and Segmentation 微調 預訓練的Mask R-CNN 模型。 它包含 170 張圖片&#xff0c;345 個行人實例。 定義數據集 用于訓練目標檢測、實例分割和人物關鍵點檢測的參考腳本允許輕松支持添加…

前端-輪詢

一、輪詢定義 輪詢是指在一定的時間間隔內&#xff0c;定時向服務器發送請求&#xff0c;獲取最新數據的過程。輪詢通常用于從服務器獲取實時更新的數據。 二、輪詢和長輪詢區別 輪詢是在固定的時間間隔內向服務器發送請求&#xff0c;即使服務器沒有數據更新也會繼續發送請求…

暴力模擬入門+簡單:零件組裝、塔子的簽到題、塔子哥考試、平均像素值、換座位

暴力模擬入門 P1038 小紅書-2022.9.23-零件組裝 #include <bits/stdc.h> #include <cstdint> using namespace std;typedef long long LL; const int N 100001; int num[4]; LL d; vector<vector<LL>> v(4, vector<LL>(N));int main() {for(in…

解決Pycharm的Settings中Project不見了也無法選擇Python Interpreter的方法

目錄 一、問題如下二、解決方法 一、問題如下 突然打開項目沒有python解釋器&#xff0c;也無法重新配置python Interpreter&#xff0c;而且整個文件夾是黃色高亮的形式&#xff0c;如下顯示&#xff0c;而且重新安裝了pycharm也沒用甚至說打開File–>Setting–>Projec…

日常BUG——普通頁面跳轉tabbar頁面報錯

&#x1f61c;作 者&#xff1a;是江迪呀??本文關鍵詞&#xff1a;日常BUG、BUG、問題分析??每日 一言 &#xff1a;存在錯誤說明你在進步&#xff01; 一、問題描述 微信小程序頁面跳轉的時候出現下面的問題&#xff1a; wx.redirectTo({url: /pages/index/i…