最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Python 中的“垃圾”是怎么回收的?

2020-07-20 10:14 作者:自學(xué)Python的小姐姐呀  | 我要投稿

前言

對于python來說,一切皆為對象,所有的變量賦值都遵循著對象引用機(jī)制。程序在運(yùn)行的時候,需要在內(nèi)存中開辟出一塊空間,用于存放運(yùn)行時產(chǎn)生的臨時變量;計算完成后,再將結(jié)果輸出到永久性存儲器中。如果數(shù)據(jù)量過大,內(nèi)存空間管理不善就很容易出現(xiàn) OOM(out of memory),俗稱爆內(nèi)存,程序可能被操作系統(tǒng)中止。

而對于服務(wù)器,內(nèi)存管理則顯得更為重要,不然很容易引發(fā)內(nèi)存泄漏- 這里的泄漏,并不是說你的內(nèi)存出現(xiàn)了信息安全問題,被惡意程序利用了,而是指程序本身沒有設(shè)計好,導(dǎo)致程序未能釋放已不再使用的內(nèi)存。- 內(nèi)存泄漏也不是指你的內(nèi)存在物理上消失了,而是意味著代碼在分配了某段內(nèi)存后,因為設(shè)計錯誤,失去了對這段內(nèi)存的控制,從而造成了內(nèi)存的浪費(fèi)。也就是這塊內(nèi)存脫離了gc的控制

計數(shù)引用

因為python中一切皆為對象,你所看到的一切變量,本質(zhì)上都是對象的一個指針。

當(dāng)一個對象不再調(diào)用的時候,也就是當(dāng)這個對象的引用計數(shù)(指針數(shù))為 0 的時候,說明這個對象永不可達(dá),自然它也就成為了垃圾,需要被回收??梢院唵蔚睦斫鉃闆]有任何變量再指向它。

代碼


  1. import os

  2. import psutil

  3. # 顯示當(dāng)前 python 程序占用的內(nèi)存大小

  4. def show_memory_info(hint):

  5. pid = os.getpid()

  6. p = psutil.Process(pid)

  7. info = p.memory_full_info()

  8. memory = info.uss / 1024./ 1024

  9. print('{} memory used: {} MB'.format(hint, memory))

  10. def func():

  11. show_memory_info('initial')

  12. a = [i for i in range(10000000)]

  13. show_memory_info('after a created')

  14. func()

  15. show_memory_info('finished')

  16. ########## 輸出 ##########

  17. initial memory used: 47.19140625 MB

  18. after a created memory used: 433.91015625 MB

  19. finished memory used: 48.109375 MB


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


代碼


  1. def func():

  2. show_memory_info('initial')

  3. global a

  4. a = [i for i in range(10000000)]

  5. show_memory_info('after a created')

  6. func()

  7. show_memory_info('finished')

  8. ########## 輸出 ##########

  9. initial memory used: 48.88671875 MB

  10. after a created memory used: 433.94921875 MB

  11. finished memory used: 433.94921875 MB


新的這段代碼中,global a 表示將 a 聲明為全局變量。那么,即使函數(shù)返回后,列表的引用依然存在,于是對象就不會被垃圾回收掉,依然占用大量內(nèi)存。同樣,如果我們把生成的列表返回,然后在主程序中接收,那么引用依然存在,垃圾回收就不會被觸發(fā),大量內(nèi)存仍然被占用著:

代碼


  1. def func():

  2. show_memory_info('initial')

  3. a = [i for i in derange(10000000)]

  4. show_memory_info('after a created')

  5. return a

  6. a = func()

  7. show_memory_info('finished')

  8. ########## 輸出 ##########

  9. initial memory used: 47.96484375 MB

  10. after a created memory used: 434.515625 MB

  11. finished memory used: 434.515625 MB


那怎么可以看到變量被引用了多少次呢?通過?sys.getrefcount


代碼


  1. import sys

  2. a = []

  3. # 兩次引用,一次來自 a,一次來自 getrefcount

  4. print(sys.getrefcount(a))

  5. def func(a):

  6. # 四次引用,a,python 的函數(shù)調(diào)用棧,函數(shù)參數(shù),和 getrefcount

  7. print(sys.getrefcount(a))

  8. func(a)

  9. # 兩次引用,一次來自 a,一次來自 getrefcount,函數(shù) func 調(diào)用已經(jīng)不存在

  10. print(sys.getrefcount(a))

  11. ########## 輸出 ##########

  12. 2

  13. 4

  14. 2


如果其中涉及函數(shù)調(diào)用,會額外增加兩次1. 函數(shù)棧2. 函數(shù)調(diào)用

從這里就可以看到python不再需要像C那種的認(rèn)為的釋放內(nèi)存,但是python同樣給我們提供了手動釋放內(nèi)存的方法?gc.collect()


代碼


  1. import gc

  2. show_memory_info('initial')

  3. a = [i for i in range(10000000)]

  4. show_memory_info('after a created')

  5. del a

  6. gc.collect()

  7. show_memory_info('finish')

  8. print(a)

  9. ########## 輸出 ##########

  10. initial memory used: 48.1015625 MB

  11. after a created memory used: 434.3828125 MB

  12. finish memory used: 48.33203125 MB

  13. ---------------------------------------------------------------------------

  14. NameErrorTraceback(most recent call last)

  15. <ipython-input-12-153e15063d8a> in<module>

  16. 11

  17. 12 show_memory_info('finish')

  18. ---> 13print(a)

  19. NameError: name 'a'isnotdefined


截止目前,貌似python的垃圾回收機(jī)制非常的簡單,只要對象引用次數(shù)為0,必定為觸發(fā)gc,那么引用次數(shù)為0是否是觸發(fā)gc的充要條件呢?

循環(huán)回收

如果有兩個對象,它們互相引用,并且不再被別的對象所引用,那么它們應(yīng)該被垃圾回收嗎?


代碼


  1. def func():

  2. show_memory_info('initial')

  3. a = [i for i in range(10000000)]

  4. b = [i for i in range(10000000)]

  5. show_memory_info('after a, b created')

  6. a.append(b)

  7. b.append(a)

  8. func()

  9. show_memory_info('finished')

  10. ########## 輸出 ##########

  11. initial memory used: 47.984375 MB

  12. after a, b created memory used: 822.73828125 MB

  13. finished memory used: 821.73046875 MB


從結(jié)果顯而易見,它們并沒有被回收,但是從程序上來看,當(dāng)這個函數(shù)結(jié)束的時候,作為局部變量的a,b就已經(jīng)從程序意義上不存在了。但是因為它們的互相引用,導(dǎo)致了它們的引用數(shù)都不為0。

這時要如何規(guī)避呢1. 從代碼邏輯上進(jìn)行整改,避免這種循環(huán)引用2. 通過人工回收


代碼:


  1. import gc

  2. def func():

  3. show_memory_info('initial')

  4. a = [i for i in range(10000000)]

  5. b = [i for i in range(10000000)]

  6. show_memory_info('after a, b created')

  7. a.append(b)

  8. b.append(a)

  9. func()

  10. gc.collect()

  11. show_memory_info('finished')

  12. ########## 輸出 ##########

  13. initial memory used: 49.51171875 MB

  14. after a, b created memory used: 824.1328125 MB

  15. finished memory used: 49.98046875 MB


python針對循環(huán)引用,有它的自動垃圾回收算法1. 標(biāo)記清除(mark-sweep)算法2. 分代收集(generational)

標(biāo)記清除

標(biāo)記清除的步驟總結(jié)為如下步驟1. GC會把所有的『活動對象』打上標(biāo)記2. 把那些沒有標(biāo)記的對象『非活動對象』進(jìn)行回收

那么python如何判斷何為非活動對象?

通過用圖論來理解不可達(dá)的概念。對于一個有向圖,如果從一個節(jié)點出發(fā)進(jìn)行遍歷,并標(biāo)記其經(jīng)過的所有節(jié)點;那么,在遍歷結(jié)束后,所有沒有被標(biāo)記的節(jié)點,我們就稱之為不可達(dá)節(jié)點。顯而易見,這些節(jié)點的存在是沒有任何意義的,自然的,我們就需要對它們進(jìn)行垃圾回收。

但是每次都遍歷全圖,對于 Python 而言是一種巨大的性能浪費(fèi)。所以,在 Python 的垃圾回收實現(xiàn)中,mark-sweep 使用雙向鏈表維護(hù)了一個數(shù)據(jù)結(jié)構(gòu),并且只考慮容器類的對象(只有容器類對象,list、dict、tuple,instance,才有可能產(chǎn)生循環(huán)引用)。



圖中把小黑圈視為全局變量,也就是把它作為root object,從小黑圈出發(fā),對象1可直達(dá),那么它將被標(biāo)記,對象2、3可間接到達(dá)也會被標(biāo)記,而4和5不可達(dá),那么1、2、3就是活動對象,4和5是非活動對象會被GC回收。

分代回收

分代回收是一種以空間換時間的操作方式,Python將內(nèi)存根據(jù)對象的存活時間劃分為不同的集合,每個集合稱為一個代,Python將內(nèi)存分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代),他們對應(yīng)的是3個鏈表,它們的垃圾收集頻率與對象的存活時間的增大而減小。新創(chuàng)建的對象都會分配在年輕代,年輕代鏈表的總數(shù)達(dá)到上限時(當(dāng)垃圾回收器中新增對象減去刪除對象達(dá)到相應(yīng)的閾值時),Python垃圾收集機(jī)制就會被觸發(fā),把那些可以被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推,老年代中的對象是存活時間最久的對象,甚至是存活于整個系統(tǒng)的生命周期內(nèi)。同時,分代回收是建立在標(biāo)記清除技術(shù)基礎(chǔ)之上。

事實上,分代回收基于的思想是,新生的對象更有可能被垃圾回收,而存活更久的對象也有更高的概率繼續(xù)存活。因此,通過這種做法,可以節(jié)約不少計算量,從而提高 Python 的性能。

所以對于剛剛的問題,引用計數(shù)只是觸發(fā)gc的一個充分非必要條件,循環(huán)引用同樣也會觸發(fā)。

調(diào)試

可以使用 objgraph來調(diào)試程序,因為目前它的官方文檔,還沒有細(xì)讀,只能把文檔放在這供大家參閱啦~

其中兩個函數(shù)非常有用 1. show_refs() 2. show_backrefs()

總結(jié)

  1. 垃圾回收是 Python 自帶的機(jī)制,用于自動釋放不會再用到的內(nèi)存空間;

  2. 引用計數(shù)是其中最簡單的實現(xiàn),不過切記,這只是充分非必要條件,因為循環(huán)引用需要通過不可達(dá)判定,來確定是否可以回收;

  3. Python 的自動回收算法包括標(biāo)記清除和分代回收,主要針對的是循環(huán)引用的垃圾收集;

  4. 調(diào)試內(nèi)存泄漏方面, objgraph 是很好的可視化分析工具。


Python 中的“垃圾”是怎么回收的?的評論 (共 條)

分享到微博請遵守國家法律
射洪县| 西昌市| 泗阳县| 钟祥市| 梅河口市| 平和县| 洪江市| 兰考县| 广灵县| 子长县| 庆云县| 阿图什市| 若羌县| 上蔡县| 理塘县| 台湾省| 四子王旗| 长兴县| 千阳县| 萍乡市| 高要市| 巧家县| 修文县| 樟树市| 南阳市| 福清市| 林州市| 华安县| 大关县| 金秀| 安义县| 吉木乃县| 南靖县| 屯昌县| 绥阳县| 满洲里市| 横山县| 闻喜县| 安龙县| 天全县| 获嘉县|