語(yǔ)言的內(nèi)存管理是語(yǔ)言設(shè)計(jì)的一個(gè)重要方面。它是決定語(yǔ)言性能的重要因素。無(wú)論是C語(yǔ)言的手工管理,還是Java的垃圾回收,都成為語(yǔ)言最重要的特征。這里以Python語(yǔ)言為例子,說(shuō)明一門(mén)動(dòng)態(tài)類(lèi)型的、面向?qū)ο蟮恼Z(yǔ)言的內(nèi)存管理方式。
對(duì)象的內(nèi)存使用
賦值語(yǔ)句是語(yǔ)言最常見(jiàn)的功能了。但即使是最簡(jiǎn)單的賦值語(yǔ)句,也可以很有內(nèi)涵。Python的賦值語(yǔ)句就很值得研究。
a = 1
? ? ? ?整數(shù)1為一個(gè)對(duì)象。而a是一個(gè)引用。利用賦值語(yǔ)句,引用a指向?qū)ο?。Python是動(dòng)態(tài)類(lèi)型的語(yǔ)言(參考 動(dòng)態(tài)類(lèi)型 ),對(duì)象與引用分離。Python像使用“筷子”那樣,通過(guò)引用來(lái)接觸和翻動(dòng)真正的食物——對(duì)象。
引用和對(duì)象?
為了探索對(duì)象在內(nèi)存的存儲(chǔ),我們可以求助于Python的內(nèi)置函數(shù) id() 。它用于返回對(duì)象的身份(identity)。其實(shí),這里所謂的身份,就是該對(duì)象的 內(nèi)存地址 。
a = 1?
? ? ? ?print(id(a))
? ? ? ?print(hex(id(a)))
? ? ? ?在我的計(jì)算機(jī)上,它們返回的是:
11246696
? ? ? ?'0xab9c68'
? ? ? ?分別為內(nèi)存地址的十進(jìn)制和十六進(jìn)制表示。?
在Python中,整數(shù)和短小的字符,Python都會(huì)緩存這些對(duì)象,以便重復(fù)使用。當(dāng)我們創(chuàng)建多個(gè)等于1的引用時(shí),實(shí)際上是讓所有這些引用指向同一個(gè)對(duì)象。
a = 1
? ? ? ?b = 1?
? ? ? ?print(id(a))
? ? ? ?print(id(b))
? ? ? ?上面程序返回
11246696?
? ? ? ?11246696
? ? ? ?可見(jiàn)a和b實(shí)際上是指向同一個(gè)對(duì)象的兩個(gè)引用。
為了檢驗(yàn)兩個(gè)引用指向同一個(gè)對(duì)象,我們可以用 is 關(guān)鍵字。is用于判斷兩個(gè)引用所指的對(duì)象是否相同。
? ? ? ?# Truea = 1 b = 1 print(a is b) # True a = "good" b = "good" print(a is b) # False a = "very good morning" b = "very good morning" print(a is b) # False a = [] b = [] print(a is b)
? ? ? ?上面的注釋為相應(yīng)的運(yùn)行結(jié)果??梢钥吹?,由于Python緩存了整數(shù)和短字符串,因此每個(gè)對(duì)象只存有一份。比如,所有整數(shù)1的引用都指向同一對(duì)象。即使使用賦值語(yǔ)句,也只是創(chuàng)造了新的引用,而不是對(duì)象本身。長(zhǎng)的字符串和其它對(duì)象可以有多個(gè)相同的對(duì)象,可以使用賦值語(yǔ)句創(chuàng)建出新的對(duì)象。
? ? ? ?在Python中,每個(gè)對(duì)象都有存有指向該對(duì)象的引用總數(shù),即 引用計(jì)數(shù) (reference count)。
我們可以使用 sys 包中的 getrefcount() ,來(lái)查看某個(gè)對(duì)象的引用計(jì)數(shù)。需要注意的是,當(dāng)使用某個(gè)引用作為參數(shù),傳遞給getrefcount()時(shí),參數(shù)實(shí)際上創(chuàng)建了一個(gè)臨時(shí)的引用。因此,getrefcount()所得到的結(jié)果,會(huì)比期望的多1。
? ? ? ?from sys import getrefcount a = [1, 2, 3] print(getrefcount(a)) b = a print(getrefcount(b))
由于上述原因,兩個(gè)getrefcount將返回2和3,而不是期望的1和2。
? ? ? ?對(duì)象引用對(duì)象
Python的一個(gè)容器對(duì)象(container),比如表、詞典等,可以包含多個(gè)對(duì)象。實(shí)際上,容器對(duì)象中包含的并不是元素對(duì)象本身,是指向各個(gè)元素對(duì)象的引用。
我們也可以自定義一個(gè)對(duì)象,并引用其它對(duì)象:
? ? ? ?class from_obj(object):???? def __init__(self, to_obj):???????? self.to_obj = to_obj b = [1,2,3] a = from_obj(b) print(id(a.to_obj)) print(id(b))
? ? ? ?可以看到,a引用了對(duì)象b。
? ? ? ?對(duì)象引用對(duì)象,是Python最基本的構(gòu)成方式。即使是a = 1這一賦值方式,實(shí)際上是讓詞典的一個(gè)鍵值"a"的元素引用整數(shù)對(duì)象1。該詞典對(duì)象用于記錄所有的全局引用。該詞典引用了整數(shù)對(duì)象1。我們可以通過(guò)內(nèi)置函數(shù) globals() 來(lái)查看該詞典。?
當(dāng)一個(gè)對(duì)象A被另一個(gè)對(duì)象B引用時(shí),A的引用計(jì)數(shù)將增加1。
? ? ? ?from sys import getrefcount a = [1, 2, 3] print(getrefcount(a)) b = [a, a] print(getrefcount(a))
? ? ? ?
? ? ? ?由于對(duì)象b引用了兩次a,a的引用計(jì)數(shù)增加了2。
? ? ? ?容器對(duì)象的引用可能構(gòu)成很復(fù)雜的拓?fù)浣Y(jié)構(gòu)。我們可以用objgraph包來(lái)繪制其引用關(guān)系,比如
? ? ? ?x = [1, 2, 3] y = [x, dict(key1=x)] z = [y, (x, y)] import objgraph objgraph.show_refs([z], filename='ref_topo.png')
objgraph是Python的一個(gè)第三方包。安裝之前需要安裝xdot。
sudo apt-get install xdot sudo pip install objgraph
? ? ? ?objgraph官網(wǎng)?
兩個(gè)對(duì)象可能相互引用,從而構(gòu)成所謂的 引用環(huán) (reference cycle)。
a = []
? ? ? ?b = [a]
? ? ? ?a.append(b)
? ? ? ?即使是一個(gè)對(duì)象,只需要自己引用自己,也能構(gòu)成引用環(huán)。
a = []
? ? ? ?a.append(a)
? ? ? ?print(getrefcount(a))
? ? ? ?引用環(huán)會(huì)給垃圾回收機(jī)制帶來(lái)很大的麻煩,我將在后面詳細(xì)敘述這一點(diǎn)。?
評(píng)論