python 垃圾回收機制

DAY 18. python垃圾回收機制

python GC主要有三種方式

  • 引用計數
  • 標記清除
  • 分代回收

其中,以引用計數為主。

18.1 引用計數(Reference Counting)

《尋夢環游記》中說,人一生會經歷兩次死亡,一次是肉體死的時候,另一次是最后一個記得你的人也忘了你時,當一個人沒有人記得的時候,才算真的死亡。垃圾回收也是這樣,當最后一個對象的引用死亡時,這個對象就會變成垃圾對象。

引用計數的原理是在每次創建對象時都添加一個計數器,每當有引用指向這個對象時,該計數器就會加1,當引用結束計數器就會減1,當計數器為0時,該對象就會被回收。

python 中所有對象所共有的數據成員由一個叫做pyobject的結構體來保存

typedef struct _object {/* 宏,僅僅在Debag模式下才不為空 */_PyObject_HEAD_EXTRA/* 定義了一個 Py_ssize_t 類型的 ob_refcnt 用來計數 */Py_ssize_t ob_refcnt;/* 類型 */struct _typeobject *ob_type;
} PyObject;

里面的ob_refcnt就是垃圾回收用到的計數器,而Py_ssize_t是整數

pyobject中保存的是python對象中共有的數據成員,所以python創建的每一個對象都會有該屬性。

在python中可以使用from sys import getrefcount來查看引用計數的值,但一般這個值會比期望的ob_refcnt高,應為它會包含臨時應用以作為getrefcount的參數

以下情況ob_refcnt加一

  • 創建對象
  • 引用對象
  • 作為參數傳遞到函數中
  • 作為成員存儲在容器中
from sys import getrefcountfoo: int = 1
print(getrefcount(foo))  # 91 應為包含臨時引用,所以會比預期的高很多bar: int = foo
print(getrefcount(foo))  # 92 增加了一個foo的引用,所以計數加一List = []
List.append(foo)
print(getrefcount(foo))  # 93 作為成員存儲在容器中,計數加一def Foo(*agrs):print(getrefcount(foo))  # 95 作為參數傳遞給了函數計數加一,實參與形參的賦值使計數加一
Foo(foo)
print(getrefcount(foo))  # 函數生命周期結束,計數減2

以下情況,計數減一:

  • 當該對象的別名被顯式銷毀時
  • 該對象的別名被賦予新值時
  • 離開作用域時
  • 從容器中刪除時
del bar
print(getrefcount(foo))  # 92 對象的別名被顯式銷毀List.pop()
print(getrefcount(foo))  # 91 從容器中刪除foo2: int = foo
foo2 = 2
print(getrefcount(foo))  # 91 別名被賦予新值

當計數被減為0時,該對象就會被回收

class MyList(list):def __del__(self):print('該對象被回收')s = MyList()
s = []  # s是MyList實例對象唯一的引用,s指向別的對象,MyList的這個實例對象就會被立刻回收
print('end')
# 該對象被回收
# end

優點:

  • 實現簡單
  • 內存回收及時,只要沒有引用立刻回收
  • 高效對象有確定生命周期

缺點:

  • 維護計數器占用資源
  • 無法解決循環引用問題
# 循環引用
class MyList(list):def __del__(self):print('該對象被回收')if __name__ == '__main__':a = MyList()b = MyList()a.append(b)b.append(a)del adel bprint('程序結束')# 程序結束
# 該對象被回收
# 該對象被回收

a和b相互引用,造成a,b的計數始終大于0,這樣就無法使用引用計數的方法處理垃圾,針對這種情況,python使用另外一種GC機制——標記清除來回收垃圾。

18.2 標記清除(Mark-Sweep)

標記清除就是為解決循環引用產生的,應為它造成的內存開銷較大,所以在不會產生循環引用的對象上是不會使用的。

  • 哪寫對象會產生循環引用?
    只有能“引用”別的對象,才會產生循環引用,那些int,string等是不會產生的,只有“容器”,類似list,dict,class之類才可能產生,也只有這類對象才可能使用標記清除機制。

過程:

  • 去環
  • 計數為0的加入生存組,不為零的加入死亡組
  • 生存組中的元素作為root,root的可達節點從死亡組中提出
  • 回收死亡組中的對象

原理:

from sys import getrefcountclass MyList(list):def __del__(self):print('該對象被回收')a = MyList()
b = MyList()
a.append(b)
b.append(a)
print(f'a的引用計數{getrefcount(a)}')
print(f'b的引用計數{getrefcount(b)}')
del a
print(f'del a的引用計數{getrefcount(b[0])}')c = MyList()
d = MyList()
c.append(d)
d.append(c)
print(f'c的引用計數{getrefcount(c)}')
print(f'd的引用計數{getrefcount(d)}')
del c
del dprint('end')

這是一開始a,b 的情況

[外鏈圖片轉存失敗(img-dNo6yajQ-1566208695428)(image/GC_01.png)]

他們的計數都是2,cd也一樣,使用del語句會斷開變量ab與MyList()內存之間的聯系

[外鏈圖片轉存失敗(img-evQV8kJR-1566208695434)(image/GC_02.png)]

這個時候就該標記清除上場了,由于a還存在,而a中引用了b,cd相互引用但都通過del顯式清除了,所以經過標記清除,ab會被保留,cd會被清除。
標記清除的第一步是“標記”,通過兩個容器來實現————生存容器和死亡容器,python首先會檢測循環引用,這時會將所有對象的計數復制一個副本以避免破壞真實的引用計數值,然后檢查鏈表中每個相互引用的對象,把這些對象的計數都減一,這一步叫做去環。
上面ab,cd都相互引用,經過del之后,a的計數依舊是2,bcd的計數是1,去環以后a的計數是1,bcd計數為0。

經過去環以后,將所有計數為0的值(bcd)加入死亡容器,不為0的(a)加入生存容器,這時還不能直接清除死亡容器中的對象,需要二審,先遍歷生存容器中的對象,把每一個生存容器中的值作為root object,根據該對象的引用關系建立有向圖,可達節點就標記為活動對象,不可達節點就為非活動對象(就是查看生存容器中是否引用了死亡容器中的對象,如果有,就把這個對象從死亡容器解救到生存容器)。
這里a引用了死亡容器中的b,所以b會被解救。

最后,死亡容器中的對象會被清除。

  • 什么時候進行標記清除

標記清除并不像引用計數那樣是實時的,而是等待占用內存到達GC閾值的時候才會觸發

18.3 分代回收

上面說了標記回收通過生存和死亡兩個容器來實現,但這只是為了方便理解說的,在真實情況下,標記清除是依賴分代回收計數完成的。

首先,我們在python中創建的每一個對象都會被收納進一個鏈表中,python稱其為零代(Generation Zero)經過檢測循環引用,會按照規則減去有循環引用的節點的計數值,這時候部分節點的計數值大于0,也有部分節點計數值等于0,大于0的節點會被放入二代,等于0的節點經過“白障算法(write barrier)”就是上面說的二審,通過的就會放在零代,不通過的就會被清除釋放。一段時間后,使用同樣的算法遍歷一代鏈表,計數大于0的放入二代鏈表,等于0的進行白障算法檢測,通過留在一代,否則釋放,python中只有這三代鏈表,根據 “弱代假說”(新生的對象存活時間比較短,年老的對象存活時間一般較長)python GC 會將主要精力放在零代上,而觸發回收則是根據GC閾值決定的,GC閾值是被分配對象的計數值與被釋放對象的計數值之間的差異,一旦這個差異超過閾值,就會觸發零代算法,回收垃圾把剩余對象放在一代,一代也類似,但隨著代數增加,閾值會提高(弱代假說),也就是零代的垃圾回收最頻繁,一代次之,二代最少。

[外鏈圖片轉存失敗(img-lzFtarZM-1566208695440)(image/python GC機制.png)]

18.4 總結

  1. GC的工作:
    • 為新創建的對象分配內存
    • 識別垃圾對象
    • 回收垃圾對象的內存
  2. 什么是垃圾:
    • 沒有對象引用
    • 只相互引用,孤島
  3. python GC機制:
    python GC機制由三部分組成:引用計數,標記清除,分代回收,其中引用計數為主。
    • 引用計數:python所有對象的共同屬性由pyobject結構體保存,該結構體中有一個int類型的成員ob_refcnt用來實現引用計數。計數為0時對象為垃圾對象,回收內存。
      • 計數加一的情況:創建對象,對象作為函數參數傳遞,對象作為成員保存到容器中,對象增加了一個引用
      • 計數減一的情況:通過del顯式刪除對象,引用指向None或別的對象,從容器中彈出,跳出作用域如函數生命結束
      • 優點:實現簡單,實時回收內存
      • 缺點:無法解決循環引用問題,開銷大
    • 標記清除和分代回收:是為了解決引用計數無法回收相互引用的問題
      • 作用對象:只作用于可能產生相互引用的“容器對象”如list,dict,class
      • 處理過程:創建對象->加入零代鏈表->到達閾值->檢測循環引用->循環引用的節點計數減少->計數大于0的加入一代鏈表,小于零的->白障->在一代鏈表中有他的引用->不清理,保留,沒有引用,清理釋放內存。
      • 弱代假說:新生的對象一般存活時間較短,年老對象存活時間較長

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

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

相關文章

曲線連接線_荷重位移曲線儀操作使用注意事項-荷重位移曲線儀廠家

荷重位移曲線儀廣泛適用于各種按鍵及開關、DOME片、按鍵、微力彈片、硅膠按鍵、汽車開關之荷重-行程測定;Windows中英文雙語軟件,操作簡單方便,軟件流暢穩定,所有測試資料(測試條件,曲線,數據結果&#xff…

進程調度

1、策略 策略決定調度程序在何時讓什么進程運行。調度器的策略往往決定系統的整體印象,并且,還要負責優化使用處理器時間。 1.1 I/o消耗型和處理器消耗型。 進程可以被分為I/O消耗型和處理器消耗型。前者指進程的大部分時間用來提交I/O請求或者等待I/O請…

Django,Ajax,Vue實現文章評論功能

Django評論 評論復雜的地方在于需要實現點擊提交評論后評論內容需要立刻出現在下面,還要保持頁面位置不變,所以提交后不能整體刷新頁面,因為刷新以后頁面肯定在最上面,而評論一般都在最下面,所以要用到Ajax 整個過程用…

回歸分析什么時候取對數_冬蜜什么時候取,冬天取蜂蜜的方法

大家好,我現在分享的是,在冬天是在什么時候取蜜!冬天在我們南方,取蜜時間是十一月到十二月的時候,只要溫度達到15度以上,蜂蜜封蓋了就可以取蜜了,并且在冬天我們只能取一次,最晚取蜜…

Opencv與dlib聯合進行人臉關鍵點檢測與識別

前言 依賴庫:opencv 2.4.9 /dlib 19.0/libfacedetection 本篇不記錄如何配置,重點在實現上。使用libfacedetection實現人臉區域檢測,聯合dlib標記人臉特征點,最后使用opencv的FaceRecognizer實現人臉識別。 準備工作 1、配置好Op…

Category 的一些事

來源:伯樂在線 - Tsui YuenHong 鏈接:http://ios.jobbole.com/90422/ 點擊 → 申請加入伯樂在線專欄作者 新增實踐部分:偏方 Hook 進某些方法來添加功能 Category – 簡介 Category(類別)是 Objective-C 2.0 添加的新特…

python tfidf特征變換_機器學習的“萬能模板” - 數據分析

最后是文本變量。很遺憾Titanic數據集中沒有合適的文本變量。一般我們處理文本變量的方法是,合并所有的文本形成一個變量,然后調用Count Vectorizer或者TfidfVectorizer算法,將文本數據轉換成數字。大部分情況下,TfidfVectorizer比…

python實現哈希表

# python 實現哈希表class HashTable:"""哈希函數的構造解決沖突"""def __init__(self, source):self.source sourceself._index []self._val []self.table []self._mod 13def Output(self):print(self._index)print(self._val)def _create…

商品綜合評價排名

店內有很多產品,而且包含但不局限于以下指標:瀏覽量、訪客數、平均停留時長、詳情頁跳出率、下單轉化率、下單支付轉化率、支付轉化率、下單金額、下單商品件數、下單買家數、支付金額、支付商品件數、加購件數、訪客平均價值、收藏人數、客單價、搜索支…

ionic資源網站

http://ionichina.com/topic/570b1f4ecd63e4247a7cfcf3 http://doc.ionicmaterialdesign.com/#intro http://ionicmaterial.com/demo/ 10大materialhttp://www.open-open.com/news/view/192f93e轉載于:https://www.cnblogs.com/znsongshu/p/6079357.html

pytorch神經網絡因素預測_實戰:使用PyTorch構建神經網絡進行房價預測

微信公號:ilulaoshi / 個人網站:lulaoshi.info本文將學習一下如何使用PyTorch創建一個前饋神經網絡(或者叫做多層感知機,Multiple-Layer Perceptron,MLP),文中會使用PyTorch提供的自動求導功能,訓練一個神經…

SQL基本操作

SQL 操作 檢索數據 SELECT 檢索數據 -- 檢索單個列 SELECT 列名 FROM table_name;-- 檢索多個列 SELECT 列1, 列2 FROM table_name;-- 檢索所有列 SELECT * FROM table_name;-- 檢索不同的值 SELECT DISTINCT 列名 FROM table_name;限制檢索結果 -- SQL Server / Access SE…

git 忽略 部分文件夾_git提交忽略某些文件或文件夾

記得第一次用 github 提交代碼,node_modules 目錄死活傳不上去,哈哈哈,后來才知道在 .gitignore 文件里設置了忽略 node_modules 目錄上傳。是的, .gitignore 文件就是設置那些你不想用 git 一起上傳的文件和文件夾。比如剛接觸到…

Ajax實現原理詳解

Ajax:Asynchronous javascript and xml,實現了客戶端與服務器進行數據交流過程。使用技術的好處是:不用頁面刷新,并且在等待頁面傳輸數據的同時可以進行其他操作。 這就是異步調用的很好體現。首先得了解什么是異步和同步的概念。…

SpringJDBC解析3-回調函數(update為例)

PreparedStatementCallback作為一個接口,其中只有一個函數doInPrepatedStatement,這個函數是用于調用通用方法execute的時候無法處理的一些個性化處理方法,在update中的函數實現: protected int update(final PreparedStatementCr…

python上下文管理器

DAY 23. python上下文管理器 Python 的 with 語句支持通過上下文管理器所定義的運行時上下文這一概念。 此對象的實現使用了一對專門方法,允許用戶自定義類來定義運行時上下文,在語句體被執行前進入該上下文,并在語句執行完畢時退出該上下文&…

勾股定理python思路_趣叮咚編程數學揭秘:為什么勾股定理a+b=c?

我們都知道:三角形3個外角之和360度可是誰知道為什么等于360度呢?其實利用編程制作動圖演繹了解啦:那勾股定理abc又是為什么呢?還有很多有趣的數學公式都可以演繹:圓的面積公式、圓周長...通過動圖演繹原來晦澀難懂的定…

System.InvalidOperationException : 不應有 Response xmlns=''。

xml如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <Response version"2"><datacash_reference>4700203048783633</datacash_reference><information>Failed to identify the card scheme of the supp…

Navicat Premium連接SQL Server

Navicat Premium連接SQL Server 步驟&#xff1a; 激活SQL Server 服務配置SQL Server網絡配置連接SQL Server 激活SQLServer服務 直接搜索 計算機管理 點 服務和應用程序&#xff0c; 點 SQL Server配置管理器&#xff0c; 雙擊第一個SQL Server服務 不出意外的話&#xf…

mysql 單標遞歸_MySql8 WITH RECURSIVE遞歸查詢父子集的方法

背景開發過程中遇到類似評論的功能是&#xff0c;需要時用查詢所有評論的子集。不同數據庫中實現方式也不同&#xff0c;本文使用Mysql數據庫&#xff0c;版本為8.0Oracle數據庫中可使用START [Param] CONNECT BY PRIORMysql 中需要使用 WITH RECURSIVE需求找到name為張三的孩子…