2019-02-16

來(lái)源

當(dāng)一門語(yǔ)言應(yīng)用熟練以后凌简,就要向底層鉆研,搞懂其原理恃逻。如果面試大公司雏搂,比如阿里巴巴這種。他們比較喜歡問(wèn)你這些底層的東西寇损。因?yàn)閼?yīng)用這層凸郑,他們很難篩出人來(lái)。

不管何種語(yǔ)言矛市,程序運(yùn)行中都會(huì)耗費(fèi)很多資源芙沥,如果有些應(yīng)用完成以后,資源還不釋放浊吏,運(yùn)行性能就會(huì)越來(lái)越低而昨,甚至發(fā)生OOM(out of memery)問(wèn)題
無(wú)論何種垃圾收集機(jī)制, 一般都是兩階段: 垃圾檢測(cè)和垃圾回收.

在Python中, 大多數(shù)對(duì)象的生命周期都是通過(guò)對(duì)象的引用計(jì)數(shù)來(lái)管理的.

問(wèn)題: 但是存在循環(huán)引用的問(wèn)題: a 引用 b, b 引用 a, 導(dǎo)致每一個(gè)對(duì)象的引用計(jì)數(shù)都不為0, 所占用的內(nèi)存永遠(yuǎn)不會(huì)被回收

要解決循環(huán)引用: 必需引入其他垃圾收集技術(shù)來(lái)打破循環(huán)引用. Python中使用了標(biāo)記-清除以及分代收集

即, Python 中垃圾回收機(jī)制: 引用計(jì)數(shù)(主要), 標(biāo)記清除, 分代收集(輔助)

引用計(jì)數(shù), 意味著必須在每次分配和釋放內(nèi)存的時(shí)候, 加入管理引用計(jì)數(shù)的動(dòng)作
引用計(jì)數(shù)的優(yōu)點(diǎn): 最直觀最簡(jiǎn)單, 實(shí)時(shí)性, 任何內(nèi)存, 一旦沒(méi)有指向它的引用, 就會(huì)立即被回收

Python中,主要依靠gc(garbage collector)模塊的引用計(jì)數(shù)技術(shù)來(lái)進(jìn)行垃圾回收找田。所謂引用計(jì)數(shù)歌憨,就是考慮到Python中變量的本質(zhì)不是內(nèi)存中一塊存儲(chǔ)數(shù)據(jù)的區(qū)域,而是對(duì)一塊內(nèi)存數(shù)據(jù)區(qū)域的引用午阵。所以python可以給所有的對(duì)象(內(nèi)存中的區(qū)域)維護(hù)一個(gè)引用計(jì)數(shù)的屬性躺孝,在一個(gè)引用被創(chuàng)建或復(fù)制的時(shí)候享扔,讓python,把相關(guān)對(duì)象的引用計(jì)數(shù)+1底桂;相反當(dāng)引用被銷毀的時(shí)候就把相關(guān)對(duì)象的引用計(jì)數(shù)-1植袍。當(dāng)對(duì)象的引用計(jì)數(shù)減到0時(shí),自然就可以認(rèn)為整個(gè)python中不會(huì)再有變量引用這個(gè)對(duì)象籽懦,所以就可以把這個(gè)對(duì)象所占據(jù)的內(nèi)存空間釋放出來(lái)了于个。

引用計(jì)數(shù)技術(shù)在每次引用創(chuàng)建和銷毀時(shí)都要多做一些操作,這可能是一個(gè)小缺點(diǎn)暮顺,當(dāng)創(chuàng)建和銷毀很頻繁的時(shí)候難免帶來(lái)一些效率上的不足厅篓。但是其最大的好處就是實(shí)時(shí)性,其他語(yǔ)言當(dāng)中捶码,垃圾回收可能只能在一些固定的時(shí)間點(diǎn)上進(jìn)行羽氮,比如當(dāng)內(nèi)存分配失敗的時(shí)候進(jìn)行垃圾回收,而引用計(jì)數(shù)技術(shù)可以動(dòng)態(tài)地進(jìn)行內(nèi)存的管理惫恼。

一档押、變量與對(duì)象

關(guān)系圖如下:

image

image

1、變量祈纯,通過(guò)變量指針引用對(duì)象

變量指針指向具體對(duì)象的內(nèi)存空間令宿,取對(duì)象的值。

2腕窥、對(duì)象粒没,類型已知,每個(gè)對(duì)象都包含一個(gè)頭部信息(頭部信息:類型標(biāo)識(shí)符和引用計(jì)數(shù)器)

注意:

變量名沒(méi)有類型簇爆,類型屬于對(duì)象(因?yàn)樽兞恳脤?duì)象癞松,所以類型隨對(duì)象),變量引用什么類型的對(duì)象入蛆,變量就是什么類型的响蓉。

image
>>> a=123
>>> b=a
>>> id(a)
4372738752
>>> id(b)
4372738752
>>> a=456
>>> id(a)
4374261616
>>> id(b)
4372738752

PS:id()是python的內(nèi)置函數(shù),用于返回對(duì)象的身份安寺,即對(duì)象的內(nèi)存地址厕妖。

image
>>> var1=object
>>> var2=var1
>>> id(var1)
4372473808
>>> id(var2)
4372473808

3、引用所指判斷

通過(guò)is進(jìn)行引用所指判斷挑庶,is是用來(lái)判斷兩個(gè)引用所指的對(duì)象是否相同言秸。

整數(shù)

>>> a=1
>>> b=1
>>> print(a is b)
True

短字符串

>>> c="good"
>>> d="good"
>>> print(c is d)
True

長(zhǎng)字符串

>>> e="very good"
>>> f="very good"
>>> print(e is f)
False

列表

>>> g=[]
>>> h=[]
>>> print(g is h)
False

由運(yùn)行結(jié)果可知:

1、Python緩存了整數(shù)和短字符串迎捺,因此每個(gè)對(duì)象在內(nèi)存中只存有一份举畸,引用所指對(duì)象就是相同的,即使使用賦值語(yǔ)句凳枝,也只是創(chuàng)造新的引用抄沮,而不是對(duì)象本身跋核;
2、Python沒(méi)有緩存長(zhǎng)字符串叛买、列表及其他對(duì)象砂代,可以由多個(gè)相同的對(duì)象,可以使用賦值語(yǔ)句創(chuàng)建出新的對(duì)象率挣。

二刻伊、引用計(jì)數(shù)

在Python中,每個(gè)對(duì)象都有指向該對(duì)象的引用總數(shù)---引用計(jì)數(shù)

查看對(duì)象的引用計(jì)數(shù):sys.getrefcount()

In [2]: import sys

In [3]: a=[1,2,3]
In [4]: getrefcount(a)
Out[4]: 2
In [5]: b=a
In [6]: getrefcount(a)
Out[6]: 3
In [7]: getrefcount(b)
Out[7]: 3

Python的一個(gè)容器對(duì)象(比如:表椒功、詞典等)捶箱,可以包含多個(gè)對(duì)象。

In [12]: a=[1,2,3,4,5]
In [13]: b=a

In [14]: a is b
Out[14]: True
In [15]: a[0]=6   
In [16]: a
Out[16]: [6, 2, 3, 4, 5]

In [17]: a is b
Out[17]: True
In [18]: b
Out[18]: [6, 2, 3, 4, 5]

image

由上可見(jiàn)动漾,實(shí)際上丁屎,容器對(duì)象中包含的并不是元素對(duì)象本身,是指向各個(gè)元素對(duì)象的引用旱眯。
3晨川、引用計(jì)數(shù)增加
1、對(duì)象被創(chuàng)建
2键思、另外的別人被創(chuàng)建
3础爬、作為容器對(duì)象的一個(gè)元素
4、被作為參數(shù)傳遞給函數(shù):foo(x)

4吼鳞、引用計(jì)數(shù)減少
1看蚜、對(duì)象的別名被顯式的銷毀
2、對(duì)象的一個(gè)別名被賦值給其他對(duì)象
3赔桌、對(duì)象從一個(gè)窗口對(duì)象中移除供炎,或,窗口對(duì)象本身被銷毀
4疾党、一個(gè)本地引用離開(kāi)了它的作用域音诫,比如上面的foo(x)函數(shù)結(jié)束時(shí),x指向的對(duì)象引用減1雪位。

引用計(jì)數(shù)法有很明顯的優(yōu)點(diǎn):
高效
運(yùn)行期沒(méi)有停頓 可以類比一下Ruby的垃圾回收機(jī)制竭钝,也就是 實(shí)時(shí)性:一旦沒(méi)有引用,內(nèi)存就直接釋放了雹洗。不用像其他機(jī)制等到特定時(shí)機(jī)香罐。實(shí)時(shí)性還帶來(lái)一個(gè)好處:處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí)。
對(duì)象有確定的生命周期
易于實(shí)現(xiàn)
原始的引用計(jì)數(shù)法也有明顯的缺點(diǎn):

維護(hù)引用計(jì)數(shù)消耗資源时肿,維護(hù)引用計(jì)數(shù)的次數(shù)和引用賦值成正比庇茫,而不像mark and sweep等基本與回收的內(nèi)存數(shù)量有關(guān)。
無(wú)法解決循環(huán)引用的問(wèn)題螃成。A和B相互引用而再?zèng)]有外部引用A與B中的任何一個(gè)旦签,它們的引用計(jì)數(shù)都為1查坪,但顯然應(yīng)該被回收。
循環(huán)引用的示例:

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

當(dāng)Python中的對(duì)象越來(lái)越多宁炫,占據(jù)越來(lái)越大的內(nèi)存偿曙,啟動(dòng)垃圾回收(garbage collection)笑陈,將沒(méi)用的對(duì)象清除。
為了解決這兩個(gè)致命弱點(diǎn)滤灯,Python又引入了以下兩種GC機(jī)制蒸健。

標(biāo)記-清除的回收機(jī)制
  針對(duì)循環(huán)引用這個(gè)問(wèn)題,比如有兩個(gè)對(duì)象互相引用了對(duì)方汰瘫,當(dāng)外界沒(méi)有對(duì)他們有任何引用,也就是說(shuō)他們各自的引用計(jì)數(shù)都只有1的時(shí)候,如果可以識(shí)別出這個(gè)循環(huán)引用袍辞,把它們屬于循環(huán)的計(jì)數(shù)減掉的話,就可以看到他們的真實(shí)引用計(jì)數(shù)了常摧〗劣酰基于這樣一種考慮,有一種方法落午,比如從對(duì)象A出發(fā)谎懦,沿著引用尋找到對(duì)象B,把對(duì)象B的引用計(jì)數(shù)減去1溃斋;然后沿著B對(duì)A的引用回到A界拦,把A的引用計(jì)數(shù)減1,這樣就可以把這層循環(huán)引用關(guān)系給去掉了梗劫。

不過(guò)這么做還有一個(gè)考慮不周的地方享甸。假如A對(duì)B的引用是單向的, 在到達(dá)B之前我不知道B是否也引用了A梳侨,這樣子先給B減1的話就會(huì)使得B稱為不可達(dá)的對(duì)象了蛉威。為了解決這個(gè)問(wèn)題,python中常常把內(nèi)存塊一分為二走哺,將一部分用于保存真的引用計(jì)數(shù)蚯嫌,另一部分拿來(lái)做為一個(gè)引用計(jì)數(shù)的副本,在這個(gè)副本上做一些實(shí)驗(yàn)丙躏。比如在副本中維護(hù)兩張鏈表择示,一張里面放不可被回收的對(duì)象合集,另一張里面放被標(biāo)記為可以被回收(計(jì)數(shù)經(jīng)過(guò)上面所說(shuō)的操作減為0)的對(duì)象彼哼,然后再到后者中找一些被前者表中一些對(duì)象直接或間接單向引用的對(duì)象对妄,把這些移動(dòng)到前面的表里面。這樣就可以讓不應(yīng)該被回收的對(duì)象不會(huì)被回收敢朱,應(yīng)該被回收的對(duì)象都被回收了剪菱。

分代回收
  分代回收策略著眼于提升垃圾回收的效率摩瞎。研究表明,任何語(yǔ)言孝常,任何環(huán)境的編程中旗们,對(duì)于變量在內(nèi)存中的創(chuàng)建/銷毀,總有頻繁和不那么頻繁的构灸。比如任何程序中總有生命周期是全局的上渴、部分的變量。
Python將所有的對(duì)象分為0喜颁,1稠氮,2三代;
所有的新建對(duì)象都是0代對(duì)象半开;
當(dāng)某一代對(duì)象經(jīng)歷過(guò)垃圾回收隔披,依然存活,就被歸入下一代對(duì)象寂拆。

gc模塊提供一個(gè)接口給開(kāi)發(fā)者設(shè)置垃圾回收的選項(xiàng)奢米。上面說(shuō)到,采用引用計(jì)數(shù)的方法管理內(nèi)存的一個(gè)缺陷是循環(huán)引用纠永,而gc模塊的一個(gè)主要功能就是解決循環(huán)引用的問(wèn)題鬓长。

常用函數(shù):

gc.set_debug(flags)
設(shè)置gc的debug日志,一般設(shè)置為gc.DEBUG_LEAK
gc.collect([generation])
顯式進(jìn)行垃圾回收尝江,可以輸入?yún)?shù)涉波,0代表只檢查第一代的對(duì)象,1代表檢查一茂装,二代的對(duì)象怠蹂,
2代表檢查一,二少态,三代的對(duì)象城侧,如果不傳參數(shù),執(zhí)行一個(gè)full collection彼妻,也就是等于傳2嫌佑。
返回不可達(dá)(unreachable objects)對(duì)象的數(shù)目
gc.set_threshold(threshold0[, threshold1[, threshold2])
設(shè)置自動(dòng)執(zhí)行垃圾回收的頻率。
gc.get_count()
獲取當(dāng)前自動(dòng)執(zhí)行垃圾回收的計(jì)數(shù)器侨歉,返回一個(gè)長(zhǎng)度為3的列表
gc模塊的自動(dòng)垃圾回收機(jī)制
必須要import gc模塊屋摇,并且is_enable()=True 才會(huì)啟動(dòng)自動(dòng)垃圾回收。
這個(gè)機(jī)制的主要作用就是發(fā)現(xiàn)并處理不可達(dá)的垃圾對(duì)象幽邓。
垃圾回收=垃圾檢查+垃圾回收

在Python中炮温,采用分代收集的方法。把對(duì)象分為三代牵舵,一開(kāi)始柒啤,對(duì)象在創(chuàng)建的時(shí)候倦挂,放在一代中,如果在一次一代的垃圾檢查中担巩,改對(duì)象存活下來(lái)方援,就會(huì)被放到二代中,同理在一次二代的垃圾檢查中涛癌,該對(duì)象存活下來(lái)犯戏,就會(huì)被放到三代中。

gc模塊里面會(huì)有一個(gè)長(zhǎng)度為3的列表的計(jì)數(shù)器拳话,可以通過(guò)gc.get_count()獲取先匪。

現(xiàn)在電腦的資源已經(jīng)沒(méi)有那么緊張了,很多語(yǔ)言不像C語(yǔ)言那樣假颇,要手動(dòng)釋放資源胚鸯,不然很容易出現(xiàn)野指針。Java, Python內(nèi)存都是自動(dòng)管理笨鸡,自動(dòng)回收垃圾。
如果我們知道這些原理坦冠,在寫敲代碼的時(shí)候形耗,就會(huì)避免一些陷阱,能夠提高程序性能辙浑。

如果垃圾回收不是那么好理解激涤,我們可以用現(xiàn)實(shí)生活中的例子來(lái)聯(lián)想。
又提到了開(kāi)發(fā)商來(lái)拍地判呕。
每拍一快地倦踢,房管局都要登記一下,這個(gè)開(kāi)發(fā)商捂地的數(shù)量又加一侠草。如果這塊地使用完了辱挥,開(kāi)發(fā)商捂地的數(shù)目減一。這便是引用計(jì)數(shù)边涕。
如果這塊地晤碘,一女二嫁,有糾紛功蜓。陷入循環(huán)死結(jié)了园爷。政府就各個(gè)擊破,請(qǐng)他們喝茶式撼,請(qǐng)他們都退出童社。這個(gè)就是標(biāo)記-清除。
如果這塊地的開(kāi)發(fā)商后臺(tái)特別強(qiáng)大著隆,捂著就是不開(kāi)發(fā)扰楼。地方政府就只能采取一貫的做法甘改,“讓后代去解決”。這就是分代回收灭抑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末十艾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子腾节,更是在濱河造成了極大的恐慌忘嫉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件案腺,死亡現(xiàn)場(chǎng)離奇詭異庆冕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)劈榨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門访递,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人同辣,你說(shuō)我怎么就攤上這事拷姿。” “怎么了旱函?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵响巢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我棒妨,道長(zhǎng)踪古,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任券腔,我火速辦了婚禮伏穆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纷纫。我一直安慰自己枕扫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布涛酗。 她就那樣靜靜地躺著铡原,像睡著了一般。 火紅的嫁衣襯著肌膚如雪商叹。 梳的紋絲不亂的頭發(fā)上燕刻,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音剖笙,去河邊找鬼卵洗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的过蹂。 我是一名探鬼主播十绑,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酷勺!你這毒婦竟也來(lái)了本橙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脆诉,失蹤者是張志新(化名)和其女友劉穎甚亭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體击胜,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亏狰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偶摔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暇唾。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辰斋,靈堂內(nèi)的尸體忽然破棺而出策州,到底是詐尸還是另有隱情,我是刑警寧澤亡呵,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布抽活,位于F島的核電站,受9級(jí)特大地震影響锰什,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丁逝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一汁胆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霜幼,春花似錦嫩码、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至琢感,卻和暖如春丢间,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驹针。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工烘挫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柬甥。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓饮六,卻偏偏與公主長(zhǎng)得像其垄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卤橄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 包(lib)绿满、模塊(module) 在Python中,存在包和模塊兩個(gè)常見(jiàn)概念窟扑。 模塊:編寫Python代碼的py...
    清清子衿木子水心閱讀 3,805評(píng)論 0 27
  • 1.什么是垃圾回收喇颁? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡(jiǎn)欲明心閱讀 89,492評(píng)論 17 311
  • [TOC] 內(nèi)存管理 一、托管堆基礎(chǔ) 在面向?qū)ο笾泄枷ィ總€(gè)類型代表一種可使用的資源无牵,要使用該資源,必須為代表資源的類...
    _秦同學(xué)_閱讀 3,804評(píng)論 0 3
  • 一厂抖、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,744評(píng)論 0 10
  • 今天下午老家的群里又來(lái)了一重要消息:只要是XX戶口茎毁,不管有沒(méi)有工作單位,繳清九萬(wàn)多忱辅,男年滿六十歲就可領(lǐng)九百多七蜘,女年...
    夜貓讀寫閱讀 255評(píng)論 0 2