Python內存管理以及垃圾回收機制

垃圾回收:用通俗點的語言解釋就是內存管理和垃圾回收的過程.

大管家refchain

在Python的C源碼中有一個名為refchain的環狀雙向鏈表,這個鏈表就比較厲害了,因為Python程序中一旦創建對象都會把這個對象添加到refchain這個鏈表中。也就是說他保存著所有的對象。例如:

age = 18
hobby = "python"

引用計數器

在refchain中的所有對象內部都有一個ob_refcnt用來保存當前對象的引用計數器,顧名思義就是自己被引用的次數,例如:

age = 18
hobby = 'python'
hobby_1 = hobby

上述代碼表示內存中有 18 和 “python” 兩個值,他們的引用計數器分別為:1、2 。

當值被多次引用的時候,不會在內存中重復創建數據,而是引用計數器+1?。 當對象被銷毀得時候同時會讓引用計數器-1?,如果引用計數器為0,則將對象從refchain鏈表中摘除,同時在內存中進行銷毀(暫不考慮緩存等特殊情況)。

age = 18
number = age  #對象18的引用計數器 + 1
del age  #對象18的引用計數器 - 1def run(arg):print(arg)run(number)  #剛開始執行函數時,對象18引用計數器 + 1,當函數執行完畢之后,對象18引用計數器 - 1 。
num_list = [11,22,number]  #對象18的引用計數器 + 1

當發生以下四種情況的時候,該對象的引用計數器+1

  • 對象被創建 ?a=14
  • 對象被引用??b=a
  • 對象被作為參數,傳到函數中 ??func(a)
  • 對象作為一個元素,存儲在容器中 ??List={a,”a”,”b”,2}

與上述情況相對應,當發生以下四種情況時,該對象的引用計數器-1

  • 當該對象的別名被顯式銷毀時??del a
  • 當該對象的引別名被賦予新的對象, ??a=26
  • 一個對象離開它的作用域,例如 func函數執行完畢時,函數里面的局部變量的引用計數器就會減一(但是全局變量不會)
  • 將該元素從容器中刪除時,或者容器被銷毀時。

當指向該對象的內存的引用計數器為0的時候,該內存將會被Python虛擬機銷毀

標記清除&分代回收

基于引用計數器進行垃圾回收非常方便和簡單,但他還是存在循環引用的問題,導致無法正常的回收一些數據,例如:

v1 = [11,22,33]  #refchain中創建一個列表對象,由于v1=對象,所以列表引對象用計數器為1.
v2 = [44,55,66]  #refchain中再創建一個列表對象,因v2=對象,所以列表對象引用計數器為1.
v1.append(v2)    #把v2追加到v1中,則v2對應的[44,55,66]對象的引用計數器加1,最終為2.
v2.append(v1)    #把v1追加到v1中,則v1對應的[11,22,33]對象的引用計數器加1,最終為2.del v1  #引用計數器-1
del v2  #引用計數器-1

對于上述代碼會發現,執行del操作之后,沒有變量再會去使用那兩個列表對象,但由于循環引用的問題,他們的引用計數器不為0,所以他們的狀態:永遠不會被使用、也不會被銷毀。項目中如果這種代碼太多,就會導致內存一直被消耗,直到內存被耗盡,程序崩潰。

為了解決循環引用的問題,引入了標記清除技術,專門針對那些可能存在循環引用的對象進行特殊處理,可能存在循環應用的類型有列表、元組、字典、集合、自定義類等那些能進行數據嵌套的類型

標記清除:創建特殊鏈表專門用于保存 列表、元組、字典、集合、自定義類等對象,之后再去檢查這個鏈表中的對象是否存在循環引用,如果存在則讓雙方的引用計數器均 - 1 。

分代回收:對標記清除中的鏈表進行優化,將那些可能存在循引用的對象拆分到3個鏈表,鏈表稱為:0/1/2三代,每代都可以存儲對象和閾值,當達到閾值時,就會對相應的鏈表中的每個對象做一次掃描,除循環引用各自減1并且銷毀引用計數器為0的對象。

// 分代的C源碼
#define NUM_GENERATIONS 3
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0}, // 0代
{{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0}, // 1代
{{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0}, // 2代
};

特別注意:0代和1、2代的threshold和count表示的意義不同。

  • 0代,count表示0代鏈表中對象的數量,threshold表示0代鏈表對象個數閾值,超過則執行一次0代掃描檢查。
  • 1代,count表示0代鏈表掃描的次數,threshold表示0代鏈表掃描的次數閾值,超過則執行一次1代掃描檢查。
  • 2代,count表示1代鏈表掃描的次數,threshold表示1代鏈表掃描的次數閾值,超過則執行一2代掃描檢查。

情景模擬

根據C語言底層并結合圖來講解內存管理和垃圾回收的詳細過程。

第一步:當創建對象age=19時,會將對象添加到refchain鏈表中。

第二步:當創建對象num_list = [11,22]時,會將列表對象添加到 refchain 和 generations 0代中。

第三步:新創建對象使generations的0代鏈表上的對象數量大于閾值700時,要對鏈表上的對象進行掃描檢查。

當0代大于閾值后,底層不是直接掃描0代,而是先判斷2、1是否也超過了閾值。

  • 如果2、1代未達到閾值,則掃描0代,并讓1代的 count + 1 。
  • 如果2代已達到閾值,則將2、1、0三個鏈表拼接起來進行全掃描,并將2、1、0代的count重置為0.
  • 如果1代已達到閾值,則講1、0兩個鏈表拼接起來進行掃描,并將所有1、0代的count重置為0,并讓2代的 count + 1

對拼接起來的鏈表在進行掃描時,主要就是剔除循環引用和銷毀垃圾,詳細過程為:

  • 掃描鏈表,把每個對象的引用計數器拷貝一份并保存到?gc_refs中,保護原引用計數器。
  • 再次掃描鏈表中的每個對象,并檢查是否存在循環引用,如果存在則讓各自的gc_refs減 1 。
  • 再次掃描鏈表,將?gc_refs?為 0 的對象移動到unreachable鏈表中;不為0的對象直接升級到下一代鏈表中。
  • 處理unreachable鏈表中的對象的 析構函數 和 弱引用,不能被銷毀的對象升級到下一代鏈表,能銷毀的保留在此鏈表。
    • 析構函數,指的就是那些定義了__del__方法的對象,需要執行之后再進行銷毀處理。
    • 弱引用,
  • 最后將?unreachable?中的每個對象銷毀并在refchain鏈表中移除(不考慮緩存機制)。

至此,垃圾回收的過程結束。

1.5 緩存機制

從上文大家可以了解到當對象的引用計數器為0時,就會被銷毀并釋放內存。而實際上他不是這么的簡單粗暴,因為反復的創建和銷毀會使程序的執行效率變低。Python中引入了“緩存機制”機制。

例如:引用計數器為0時,不會真正銷毀對象,而是將他放到一個名為?free_list?的鏈表中,之后會再創建對象時不會在重新開辟內存,而是在free_list中將之前的對象來并重置內部的值來使用。

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

    v1 = 3.14 # 開辟內存來存儲float對象,并將對象添加到refchain鏈表。
    print( id(v1) ) # 內存地址:4436033488
    del v1 # 引用計數器-1,如果為0則在rechain鏈表中移除,不銷毀對象,而是將對象添加到float的free_list.
    v2 = 9.999 # 優先去free_list中獲取對象,并重置為9.999,如果free_list為空才重新開辟內存。
    print( id(v2) ) # 內存地址:4436033488# 注意:引用計數器為0時,會先判斷free_list中緩存個數是否滿了,未滿則將對象緩存,已滿則直接將對象銷毀。
  • int類型,不是基于free_list,而是維護一個small_ints鏈表保存常見數據(小數據池),小數據池范圍:-5 <= value < 257。即:重復使用這個范圍的整數時,不會重新開辟內存。

    v1 = 38 # 去小數據池small_ints中獲取38整數對象,將對象添加到refchain并讓引用計數器+1。
    print( id(v1)) #內存地址:4514343712
    v2 = 38 # 去小數據池small_ints中獲取38整數對象,將refchain中的對象的引用計數器+1。
    print( id(v2) ) #內存地址:4514343712# 注意:在解釋器啟動時候-5~256就已經被加入到small_ints鏈表中且引用計數器初始化為1,代碼中使用的值時直接去small_ints中拿來用并將引用計數器+1即可。另外,small_ints中的數據引用計數器永遠不會為0(初始化時就設置為1了),所以也不會被銷毀。
  • str類型,維護unicode_latin1[256]鏈表,內部將所有的ascii字符緩存起來,以后使用時就不再反復創建。

    v1 = "A"
    print( id(v1) ) # 輸出:4517720496
    del v1
    v2 = "A"
    print( id(v1) ) # 輸出:4517720496# 除此之外,Python內部還對字符串做了駐留機制,針對那么只含有字母、數字、下劃線的字符串(見源碼Objects/codeobject.c),如果內存中已存在則不會重新在創建而是使用原來的地址里(不會像free_list那樣一直在內存存活,只有內存中有才能被重復利用)。
    v1 = "wupeiqi"
    v2 = "wupeiqi"
    print(id(v1) == id(v2)) # 輸出:True
  • list類型,維護的free_list數組最多可緩存80個list對象。
    v1 = [11,22,33]
    print( id(v1) ) # 輸出:4517628816
    del v1
    v2 = ["武","沛齊"]
    print( id(v2) ) # 輸出:4517628816
  • tuple類型,維護一個free_list數組且數組容量20,數組中元素可以是鏈表且每個鏈表最多可以容納2000個元組對象。元組的free_list數組在存儲數據時,是按照元組可以容納的個數為索引找到free_list數組中對應的鏈表,并添加到鏈表中。
    v1 = (1,2)
    print( id(v1) )
    del v1 # 因元組的數量為2,所以會把這個對象緩存到free_list[2]的鏈表中。
    v2 = ("武沛齊","Alex") # 不會重新開辟內存,而是去free_list[2]對應的鏈表中拿到一個對象來使用。
    print( id(v2) )

參考:

pythonav資源分享

Pythonn內存管理以及垃圾回收機制 - 武沛齊 - 博客園

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

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

相關文章

pythonfillcolor_openpyxl 填充顏色(單元格)

如果需要填充某個單元格的顏色需要3步&#xff1a;# 1-加載庫文件from openpyxl import Workbookfrom openpyxl.styles import PatternFill#2-新建一個工作簿wb Workbook()ws wb.active#隨便賦個值d4 ws[D4]d4 43d4.value#3-設置樣式&#xff0c;并且加載到對應單元格fill …

Mint-ui中loadmore(上拉加載下拉刷新)組件在ios中滑動會觸發點擊事件的解決方法...

bug說明&#xff1a; Mint-ui中loadmore(上拉加載下拉刷新)組件 在 使用fastclick的情況下 &#xff0c;在ios設備中滑動會觸發點擊事件&#xff1b; 解決方法&#xff1a; 我是按需引入&#xff0c;去項目中找到loadmore下的index.js&#xff0c;全部引入的要找mint下面mint-u…

【Ext.Net學習筆記】01:在ASP.NET WebForm中使用Ext.Net

Ext.NET是基于跨瀏覽器的ExtJS庫和.NET Framework的一套支持ASP.NET AJAX的開源Web控件&#xff0c;包含有豐富的Ajax運用&#xff0c;其前身是Coolite。 下載地址&#xff1a;http://www.ext.net/download/ 示例地址&#xff1a;http://examples.ext.net/ 1.首先下載Ext.Net,地…

面試之操作系統

基本特征 1. 并發 并發是指宏觀上在一段時間內能同時運行多個程序&#xff0c;而并行則指同一時刻能運行多個指令。并行需要硬件支持&#xff0c;如多流水線、多核處理器或者分布式計算系統。操作系統通過引入進程和線程&#xff0c;使得程序能夠并發運行。 2. 共享 共享是指…

mysql新增列并同時增加數據_圖解MySQL | [原理解析] MySQL 為表添加列 是怎么quot;立刻quot;完成的...

在上一期圖解 圖解MySQL | MySQL DDL為什么成本高&#xff1f;中&#xff0c;我們介紹了&#xff1a;傳統情況下&#xff0c;為表添加列需要對表進行重建騰訊團隊為 MySQL 引入了 Instant Add Column 的方案(以下稱為 "立刻加列" 功能)可以快速完成 為表添加列 的任務…

GCC for Win32開發環境介紹

GCC for Win32開發環境介紹(1) 第一章 在視窗操作系統下的GCC 第一節GCC家族概覽 GCC是一個原本用于Unix-like系統下編程的編譯器。不過&#xff0c;現在GCC也有了許多Win32下的移植版本。所以&#xff0c;也許對于許多Windows開發者來說&#xff0c;GCC還是一個比較陌生的東西…

包裝函數

function wrap(object,method,wrapper){ //object:包裝方法所屬對象 method:方法名 wrapper:替換函數var fn object[method];return object[method] function(){return wrapper.apply(this,[fn.bind(this)].concat(Array.prototype.slice.call(arguments)));}; } 轉載于…

JAR——pinyin4j-2.5.0

簡介&#xff1a;將中文轉為拼音&#xff1b; 使用&#xff1a; 123//返回的是字符串String pinyin[] PinyinHelper.toHanyuPinyinStringArray(chinese);//eg:你----ni3本文轉自wauoen51CTO博客&#xff0c;原文鏈接&#xff1a;http://blog.51cto.com/7183397/1605894&#…

Android高效加載大圖、多圖解決方案,有效避免程序OOM

http://blog.csdn.net/guolin_blog/article/details/9316683轉載于:https://www.cnblogs.com/jianglijs/p/7827524.html

Flask 上下文源碼解析

簡單來說&#xff0c;上下文包括request_ctx(封裝了request和session),app_request(封裝了app和g)&#xff0c;兩個ctx都儲存在一個叫做Local的數據結構中&#xff0c;這個結構的作用就是會自動根據不同的線程id返回對應的數據&#xff0c;然后通過一個叫做 LocalStark 的結構把…

reg型變量怎么賦值_UiPath變量介紹和使用

1 變量變量主要用于存儲數據&#xff0c;它在RPA中扮演重要的數據傳遞角色&#xff0c;是RPA編程不可或缺的一部分。它包括變量名稱和變量的值&#xff0c;變量的值支持多種數據類型&#xff0c;包括從通用值&#xff0c;文本&#xff0c;數字&#xff0c;數據表&#xff0c;時…

gcc 使用教程

gcc 使用教程 目 錄 gcc makefile寫法 gcc_egcs使用 gdb使用 gcc常用選項對代碼的影響 一般情況 -O 編譯選項 -O2 編譯選項 -fomit-frame-pointer 編譯選項-fomit-frame-pointer && -O2-fPIC 編譯選項 -static 編譯選項 AT&T的匯編格式 x86內聯匯編 簡述 內聯匯編…

Struts2教程9:實現自已的攔截器

在上一篇中介紹了Struts2攔截器的原理&#xff0c;在這一篇中我們將學習一下如何編寫自己的攔截器。一、攔截器的實現實現一個攔截器非常簡單。實際上&#xff0c;一個攔截器就是一個普通的類&#xff0c;只是這個類必須實現com.opensymphony.xwork2.interceptor.Interceptor接…

標準C程序設計七---66

Linux應用 編程深入 語言編程標準C程序設計七---經典C11程序設計 以下內容為閱讀&#xff1a; 《標準C程序設計》&#xff08;第7版&#xff09; 作者&#xff1a;E. Balagurusamy&#xff08;印&#xff09;&#xff0c; 李周芳譯 清華大學出版社…

深度學習之概述

深度學習的應用場景 1、圖像應用&#xff1a; 1.1 大規模(大數據量)圖片識別(聚類/分類)&#xff0c;如人臉識別&#xff0c;車牌識別&#xff0c;OCR等。人臉識別算法&#xff1a;① faceID ② faceNet 1.2 以圖搜圖&#xff0c;圖像分割 1.3 目標檢測&#xff0…

如何根據對象獲取到對應的表名_Excel VBA 常用對象二

下面繼續講解上一節中未講完的內容&#xff1a;Excel VBA編程中常常使用的那些對象到底是什么&#xff0c;如何在代碼中表示它們。Worksheet對象Worksheet對象代表工作表。工作簿中的每個工作表都是一個Worksheet對象&#xff0c;所有Worksheet對象構成了Worksheets集合。我們使…

PIX525故障一例,求解

IDC機房網絡拓樸如下&#xff1a;IDC核心交換機-----通過一條網線-------機柜D-LNKI交換機------PIX 525------CISCO交換機------各WEB服務器。其中D-LINK交換機的IP為192.168.2.11&#xff0c;也就是下面日志中的IP。另外&#xff0c;之所以IDC和PIX之間再加一臺DLINK是因為有…

gcc教程(轉)

gcc 目 錄 gcc makefile寫法 gcc_egcs使用 gdb使用 gcc常用選項對代碼的影響 一般情況 -O 編譯選項 -O2 編譯選項 -fomit-frame-pointer 編譯選項 -fomit-frame-pointer && -O2 -fPIC 編譯選項 -static 編譯選項 AT&T的匯編格式 x86內聯匯編 簡述 內聯匯編 程序模…

深度學習之 BP 算法

神經網絡的一種求解W的算法&#xff0c;分為信號“正向傳播(FP)”求損失&#xff0c;“反向傳播(BP)”回傳誤差&#xff1b;根據誤差值修改每層的權重&#xff0c;繼續迭代。 BP算法也叫做δ算法。以三層的感知器為例&#xff08;假定現在隱層和輸出層均存在相同類型的激活函數…

python自帶的解釋器叫做_python學習

一、PYTHON中的元素1.基本元素運算符&#xff1a; - * / %等等除法&#xff1a;" / " 表示浮點數除法&#xff0c;返回浮點結果;" // " 表示整數除法,返回不大于結果的一個最大的整數運算順序&#xff1a;先乘除 再加減 括號最優先變量&#xff1a;就是一…