Python垃圾回收是如何現(xiàn)的

這篇文章主要講解了“Python垃圾回收是如何現(xiàn)的”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Python垃圾回收是如何現(xiàn)的”吧!

站在用戶的角度思考問題,與客戶深入溝通,找到烏拉特前網(wǎng)站設(shè)計(jì)與烏拉特前網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、主機(jī)域名網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋烏拉特前地區(qū)。

    什么是垃圾回收

    垃圾回收(GC) 大家應(yīng)該多多少少都了解過,什么是垃圾回收呢?垃圾回收GC的全拼是 Garbage Collection,在維基百科的定義是:在計(jì)算機(jī)科學(xué)中,垃圾回收(英語:Garbage Collection,縮寫為GC)是一種自動(dòng)的內(nèi)存管理機(jī)制。當(dāng)一個(gè)電腦上的動(dòng)態(tài)內(nèi)存不再需要時(shí),就應(yīng)該予以釋放,以讓出內(nèi)存,這種內(nèi)存資源管理,稱為垃圾回收。我們都知道在C/C++里用戶需要自己管理維護(hù)內(nèi)存,自己管理內(nèi)存是很自由,可以隨意申請、釋放內(nèi)存,但是極易會(huì)出現(xiàn)內(nèi)存泄露,懸空指針等問題;像現(xiàn)在的高級語言Java,Python等,都采用了垃圾回收機(jī)制,自動(dòng)進(jìn)行內(nèi)存管理,而垃圾回收機(jī)制專注于兩件事:① 找到內(nèi)存中無用的垃圾資源。 ② 清除這些垃圾資源并把內(nèi)存讓出來給其他對象使用。

    Python作為一門解釋型語言,因?yàn)楹唵我锥恼Z法,我們可以直接對變量賦值,而不必聲明變量的類型,變量類型的確定、內(nèi)存空間的分配與釋放都是由Python解釋器在運(yùn)行時(shí)自動(dòng)進(jìn)行的,我們不必關(guān)心;Python這一自動(dòng)管理內(nèi)存的功能極大的減少了開發(fā)者的編碼負(fù)擔(dān),讓開發(fā)者專注于業(yè)務(wù)實(shí)現(xiàn),這也是成就Python自身的重要原因之一。接下來,我們就扒一扒Python的內(nèi)存管理。

    Python中的垃圾回收機(jī)制

    引用計(jì)數(shù)

    Python中一切皆對象,也就是說,在Python中你用到的一切變量,本質(zhì)上都是類對象。實(shí)際上每一個(gè)對象的核心就是一個(gè)**「結(jié)構(gòu)體PyObject」**,它的內(nèi)部有一個(gè)引用計(jì)數(shù)器ob_refcnt,程序在運(yùn)行的過程中會(huì)實(shí)時(shí)的更新ob_refcnt的值,來反映引用當(dāng)前對象的名稱數(shù)量。當(dāng)某對象的引用計(jì)數(shù)值為0,說明這個(gè)對象變成了垃圾,那么它會(huì)被回收掉,它所用的內(nèi)存也會(huì)被立即釋放掉。

    typedef struct _object {
        int ob_refcnt;//引用計(jì)數(shù)
        struct _typeobject *ob_type;
    } PyObject;

    以下情況是導(dǎo)致引用計(jì)數(shù)加一的情況:
    ①對象被創(chuàng)建,例如a=5
    ②對象被引用,b=a
    ③對象被作為參數(shù),傳入到一個(gè)函數(shù)中(要注意的是,在函數(shù)調(diào)用發(fā)生的時(shí)候,會(huì)產(chǎn)生額外的兩次引用,一次來自函數(shù)棧,另一個(gè)是函數(shù)參數(shù))
    ④對象作為一個(gè)元素,存儲(chǔ)在容器中(例如存儲(chǔ)在列表中)

    下面的情況則會(huì)導(dǎo)致引用計(jì)數(shù)減一:
    ①對象別名被顯示銷毀 del a
    ②對象別名被賦予新的對象
    ③一個(gè)對象離開它的作用域
    ④對象所在的容器被銷毀或者是從容器中刪除對象

    我們還可以通過sys包中的getrefcount()來獲取一個(gè)名稱所引用的對象當(dāng)前的引用計(jì)數(shù)(注意,這里getrefcount()本身會(huì)使得引用計(jì)數(shù)加一)

    import sys
    a = [1, 2, 3]
    print(sys.getrefcount(a))
    # 輸出為2,說明有兩次引用(一次來自a的定義,一次來自getrefcount)
    
    def func(a):
        print(sys.getrefcount(a))
        # 輸出為4,說明有四次引用(a的定義、Python的函數(shù)調(diào)用棧,函數(shù)參數(shù),和getrefcount)
    
    func(a)
    print(sys.getrefcount(a))
    # 輸出為2,說明有兩次引用(一次來自a的定義,一次來自getrefcount),此時(shí)函數(shù)func調(diào)用已經(jīng)不存在

    下面從使用內(nèi)存的角度看一下:

    import os
    import psutil
    
    
    def show_memory_info(hint):
        """
        顯示當(dāng)前 python 程序占用的內(nèi)存大小
        :param hint:
        :return:
        """
        pid = os.getpid()
        p = psutil.Process(pid)
    
        info = p.memory_full_info()
        memory = info.rss / 1024 / 1024
        print("{} 當(dāng)前進(jìn)程的內(nèi)存使用: {} MB".format(hint, memory))
    
    
    def func():
        show_memory_info("初始")
        a = [i for i in range(9999999)]
        show_memory_info("創(chuàng)建a之后")
    
    
    func()
    show_memory_info("結(jié)束")

    輸出如下:

    初始 當(dāng)前進(jìn)程的內(nèi)存使用: 12.125 MB
    創(chuàng)建a之后 當(dāng)前進(jìn)程的內(nèi)存使用: 205.15625 MB
    結(jié)束 當(dāng)前進(jìn)程的內(nèi)存使用: 12.87890625 MB

    可以看出,當(dāng)前進(jìn)程初始的內(nèi)存使用為12.125 MB,當(dāng)調(diào)用了函數(shù)func()創(chuàng)建列表a之后,內(nèi)存占用迅速增加到了205.15625 MB,而在函數(shù)調(diào)用結(jié)束后,內(nèi)存則返回正常。這是因?yàn)?,函?shù)內(nèi)部聲明的列表a是局部變量,在函數(shù)返回后,局部變量的引用會(huì)注銷掉,此時(shí)列表a所指代對象的引用計(jì)數(shù)為0,Python 便會(huì)執(zhí)行垃圾回收,因此之前占用的大量內(nèi)存就又回來了。

    循環(huán)引用

    何為循環(huán)引用?簡單來說就是兩個(gè)對象相互引用。看下面一段程序:

    def func2():
        show_memory_info("初始")
        a = [i for i in range(10000000)]
        b = [x for x in range(10000001, 20000000)]
        a.append(b)
        b.append(a)
        show_memory_info("創(chuàng)建a,b之后")
    
    func2()
    show_memory_info("結(jié)束")

    輸出如下:

    初始 當(dāng)前進(jìn)程的內(nèi)存使用: 12.14453125 MB
    創(chuàng)建a,b之后 當(dāng)前進(jìn)程的內(nèi)存使用: 396.6875 MB
    結(jié)束 當(dāng)前進(jìn)程的內(nèi)存使用: 396.96875 MB

    可以看出,在程序中,a和b互相引用,并且作為局部變量在函數(shù)func2調(diào)用結(jié)束后,a和b從程序意義上已經(jīng)不存在,但從輸出結(jié)果中看到,依然有內(nèi)存占用,這是為什么呢?因?yàn)榛ハ嘁脤?dǎo)致它們的引用數(shù)都不為0。

    如果在生產(chǎn)環(huán)境下出現(xiàn)了循環(huán)引用,又沒有其他垃圾回收機(jī)制的情況下,經(jīng)過長時(shí)間運(yùn)行后,程序所占用的內(nèi)存一定會(huì)變得越來越大,如果沒有被及時(shí)處理,一定會(huì)跑滿服務(wù)器的。

    如果不得不使用循環(huán)引用的話,我們可以顯式調(diào)用gc.collect() 來啟動(dòng)垃圾回收:

    def func2():
        show_memory_info("初始")
        a = [i for i in range(10000000)]
        b = [x for x in range(10000001, 20000000)]
        a.append(b)
        b.append(a)
        show_memory_info("創(chuàng)建a,b之后")
    
    func2()
    gc.collect()
    show_memory_info("結(jié)束")

    輸出如下:

    初始 當(dāng)前進(jìn)程的內(nèi)存使用: 12.29296875 MB
    創(chuàng)建a,b之后 當(dāng)前進(jìn)程的內(nèi)存使用: 396.69140625 MB
    結(jié)束 當(dāng)前進(jìn)程的內(nèi)存使用: 12.95703125 MB

    引用計(jì)數(shù)機(jī)制有高效、簡單、實(shí)時(shí)性(一旦為零就直接做掉)等優(yōu)點(diǎn),一旦一個(gè)對象的引用計(jì)數(shù)歸零,內(nèi)存就直接釋放了。不用像其他機(jī)制等到特定時(shí)機(jī)。將垃圾回收隨機(jī)分配到運(yùn)行的階段,處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí),正常程序的運(yùn)行比較平穩(wěn)。但是,引用計(jì)數(shù)也存在著一些缺點(diǎn),通常的缺點(diǎn)有:

    ① 邏輯雖然簡單,但維護(hù)起來有些麻煩。每個(gè)對象需要分配單獨(dú)的空間來統(tǒng)計(jì)引用計(jì)數(shù),并且需要對引用計(jì)數(shù)進(jìn)行維護(hù),這是需要消耗一下資源的。
    ② 循環(huán)引用。這將是引用計(jì)數(shù)機(jī)制的致命傷,引用計(jì)數(shù)對此是無解的,因此必須要使用其它的垃圾回收算法對其進(jìn)行補(bǔ)充。

    事實(shí)上,Python 使用標(biāo)記清除(mark-sweep)算法和分代收集(generational),來啟用針對循環(huán)引用的自動(dòng)垃圾回收。

    標(biāo)記清除解除循環(huán)引用

    Python采用了 標(biāo)記-清除(Mark and Sweep)算法,解決容器對象可能產(chǎn)生的循環(huán)引用問題。(注意,只有容器類對象才有可能產(chǎn)生循環(huán)引用,比如列表、字典、用戶自定義類的對象、元組等。而像數(shù)字,字符串這類簡單類型不會(huì)出現(xiàn)循環(huán)引用。作為一種優(yōu)化策略,對于只包含簡單類型的元組也不在標(biāo)記清除算法的考慮之列)

    它分為兩個(gè)階段:第一階段是標(biāo)記階段,GC會(huì)把所有的活動(dòng)對象打上標(biāo)記,第二階段是把那些沒有標(biāo)記的非活動(dòng)對象進(jìn)行回收。

    那么Python又是如何判斷什么樣的對象為非活動(dòng)對象的呢?

    對于任何對象集合,我們先建個(gè)引用計(jì)數(shù)副本表,來存它們的引用計(jì)數(shù),然后把集合內(nèi)部的引用都解除掉(內(nèi)部引用是指這個(gè)集合中的某個(gè)對象引用了本集合內(nèi)部的另一個(gè)對象),解除的過程中在副本表減少引用計(jì)數(shù),解除掉所有的內(nèi)部引用后,在副本表引用計(jì)數(shù)依然不為0的,就是根集合,然后開始標(biāo)記過程,即從跟集合節(jié)點(diǎn)逐步恢復(fù)引用并增加副本表的引用計(jì)數(shù),最后副本表中引用計(jì)數(shù)為0的,就是垃圾對象了,我們就需要對它們進(jìn)行垃圾回收。例如:

    Python垃圾回收是如何現(xiàn)的

    上面這個(gè)集合中的節(jié)點(diǎn)有外部進(jìn)來的連接(到a和到b),也有到外部的連接(c引用了外面某個(gè)對象),右邊是引用計(jì)數(shù)表,然后我們拆掉所有內(nèi)部連接:

    Python垃圾回收是如何現(xiàn)的

    那么根集合就是a和b了,然后我們從a和b出發(fā)開始標(biāo)記并恢復(fù)引用計(jì)數(shù):

    Python垃圾回收是如何現(xiàn)的

    從a和b出發(fā)可達(dá)的節(jié)點(diǎn)都被恢復(fù)了,引用計(jì)數(shù)還是0的就是這個(gè)集合內(nèi)部循環(huán)引用的垃圾(e和f),如果把所有對象看做一個(gè)集合,那么可以回收所有垃圾,也可以將所有對象劃分成一個(gè)個(gè)小的集合,分別回收小集合內(nèi)的垃圾。
    但是每次都需要遍歷圖,對于Python而言是一種巨大的性能浪費(fèi)。

    分代回收

    分代回收是一種以空間換時(shí)間的操作方式,Python將內(nèi)存根據(jù)對象的存活時(shí)間劃分為不同的集合,每個(gè)集合稱為一個(gè)代,Python將內(nèi)存分為了3代,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代)。它們對應(yīng)3個(gè)鏈表,它們的垃圾收集頻率隨對象的存活時(shí)間的增大而減小。

    新創(chuàng)建的對象都會(huì)分配在年輕代,年輕代鏈表的總數(shù)達(dá)到上限時(shí),即當(dāng)垃圾回收器中新增對象減去刪除對象達(dá)到相應(yīng)的閾值時(shí),就會(huì)對這一代對象啟動(dòng)垃圾回收,把那些可以被回收的對象回收掉,而那些不會(huì)回收的對象就會(huì)被移到中年代去,依此類推,老年代中的對象是存活時(shí)間最久的對象,甚至是存活于整個(gè)系統(tǒng)的生命周期內(nèi)。同時(shí),分代回收是建立在標(biāo)記清除技術(shù)基礎(chǔ)之上。事實(shí)上,分代回收基于的思想是,新生的對象更有可能被垃圾回收,而存活更久的對象也有更高的概率繼續(xù)存活。因此,通過這種做法,可以節(jié)約不少計(jì)算量,從而提高Python的性能。

    感謝各位的閱讀,以上就是“Python垃圾回收是如何現(xiàn)的”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Python垃圾回收是如何現(xiàn)的這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

    名稱欄目:Python垃圾回收是如何現(xiàn)的
    本文URL:http://muchs.cn/article18/pdpcdp.html

    成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、網(wǎng)站設(shè)計(jì)、網(wǎng)站改版網(wǎng)頁設(shè)計(jì)公司、網(wǎng)站內(nèi)鏈網(wǎng)站策劃

    廣告

    聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

    h5響應(yīng)式網(wǎng)站建設(shè)