【python測試開發棧】python內存管理機制(一)—引用計數

什麼是內存

在開始進入正題以前,咱們先來回憶下,計算機基礎原理的知識,爲何須要內存。咱們都知道計算機的CPU至關於人類的大腦,其運算速度很是的快,而咱們平時寫的數據,好比:文檔、代碼等都是存儲在磁盤上的。磁盤的存取速度徹底不能匹配cpu的運算速度,所以就須要一箇中間層來適配二者的不對等,內存由此而來,內存的存取速率很快,可是存儲空間不大。java

舉一個圖書館的例子,便於你們理解,咱們圖書館的書架就至關於磁盤,存放了大量的圖書能夠供咱們閱讀,可是若是書放在書架上,咱們沒辦法直接閱讀(效率低),只能將書取出來,放在書桌上看,那書桌就至關於內存。python

內存回收

內存資源畢竟是有限的,因此在使用以後,必須被回收掉,不然系統運行一段時間後就會因無內存可用而癱瘓。咱們軟件測試領域經常使用的兩種語言:java和python,所有都採用內存自動回收的方法,也就是咱們只管申請內存,可是無論釋放內存,由jvm和python解釋器來按期觸發內存回收。做爲對比,C語言和C++中,程序員須要使用malloc申請內存,使用free去釋放內存,malloc和free必須成對的出現,不然很是容易出現內存問題。程序員

還拿上面圖書館的例子,假如圖書館的書看完以後放在書桌上就能夠(由於圖書可自動回收),那麼很快的,就沒有位置給新進來的同窗看書了。這時候就須要圖書館管理員(jvm或python解釋器)按期的回收圖書,清空書桌。不過正常狀況下,咱們離開圖書館時,要本身清空書桌,將書放回書架(相似C語言和C++的內存回收方式)。緩存

python內存管理

引用計數

python經過引用計數來進行內存管理,每個python對象,都維護了一個指向該對象的引用計數。python的sys庫提供了getrefcount()函數來獲取對象的引用計數。下面咱們看個例子(注意:不一樣版本的python,運行結果不一樣,我這裏採用的是python3.7.4):bash

"""
    @author: xuanke
    @time: 2019/11/27
    @function: 測試python內存
"""
import sys

class RefClass(object):
    def __init__(self):
        print("this is init")

def ref_count_test():
    # 驗證普通字符串
    str1 = "abc"
    print(sys.getrefcount(str1))
    # 驗證稍微複雜點的字符串
    print(sys.getrefcount("xuankeTester"))
    # 驗證小的數字
    print(sys.getrefcount(12))
    # 驗證大的數字
    print(sys.getrefcount(257))
    # 驗證類
    a = RefClass()
    print(sys.getrefcount(a))
    # 驗證引用計數增長
    b = a
    print(sys.getrefcount(a))

    # 驗證引用計數減小
    b = None
    print(sys.getrefcount(a))

if __name__ == '__main__':
    ref_count_test()

你們先來思考下,最終的結果會是什麼?!我以爲應該不少人都會答錯,由於不一樣版本的python,對引用變量個數有影響(主要是可複用的對象)。咱們先貼出來運行結果,再來分析產生結果的緣由:python2.7

27
4
9
3
this is init
2
3
2

不過提早聲明一點:sys.getrefcount函數在使用時,由於將對象(好比上例中的str1)做爲參數傳入,因此會額外增長一個變量(至關於getrefcount持有了str1的引用),所以實際每一個對象的實際引用計數都得減1。下面分別介紹下上面的幾種狀況:jvm

  • 字符串: str1='abc'的引用數是27-1=26,是由於字符串'abc'比較簡單,在python解釋器(CPython)中確實可能存在26個引用。做爲對比,在python2.7中,str1的引用變量個數是3-1=2。而字符串'xuanketester',是我自定義的一個字符串,因此不可能會有其餘額外的引用,因此其引用變量個數是3-1=2(至於爲何是2,理論應該是0,是由於python解釋器默認持有了全部字符串的兩個引用)。
  • 數字: 數字12對應的引用計數個數是9-1=8,而257對應的引用計數個數是3-1=2,這主要是由於,在python初始化過程當中,就建立了從-5到256的數字,緩存起來,這樣作是爲了頻繁的分配內存,提升效率。而對於不在這個區間的數字,則會從新分配內存空間。因此數字12由於被複用,其引用計數個數是8(在python2.7.14中,其引用計數個數是8)。
  • 類: 在上面例子中,建立一個RefClass對象,其引用計數就是2-1=1,由於其是一個咱們自定義的類對象,在python解釋器(Cpython)中確定不會被複用。

咱們能夠經過打印內存地址的方式來驗證上面這幾種狀況:函數

def memory_address_test():
    str1 = 'xuankeTester'
    str2 = 'xuankeTester'
    print(id(str1))
    print(id(str2))

    str3 = 'abc'
    str4 = 'abc'
    print(id(str3))
    print(id(str4))

    a = 12
    b = 12
    print(id(a))
    print(id(b))

    c = 257
    d = 257
    print(id(c))
    print(id(d))

按照咱們上面的分析,c和d的地址應該是不同的,a和b的地址是同樣的,字符串str1和str二、str3和str4內存地址都是同樣的。可是我在pycharm中,直接運行py文件,結果卻和預想的不一致,結果以下:測試

2854496960176
2854496960176
2854496857840
2854496857840
140724423258720
140724423258720
2854498931120
2854498931120

全部狀況的內存地址都是同樣的,這是爲何呢?我考慮到是否是pycharm對py文件作了優化,因而我又在命令行嘗試執行,結果仍是同樣的。因此,我猜想多是python解釋器在執行文件時,爲了提升py文件的執行效率,對文件的內存地址作了優化—相同內容的對象內存地址都同樣。優化

爲了驗證這個想法,我直接在python交互模式下執行,果真獲得了我想要的結果:

Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> a=12
>>> b=12
>>> id(a)
140724423258720
>>> id(b)
140724423258720
>>> a=257
>>> b=257
>>> id(a)
2559155778384
>>> id(b)
2559155778192
>>> a='xuankeTester'
>>> b='xuankeTester'
>>> id(a)
2559155711280
>>> id(b)
2559155711280
>>>

從上面能夠看到兩個257對應的地址確實是不同的,和咱們最初判斷的是一致的。

總結

python經過對象的引用計數來管理內存,其實java的JVM也有用引用計數,因此理解了引用計數,爲咱們理解python的垃圾回收方法打下了基礎。本計劃這一篇文章就將python內存管理的機制講完的,可是發現一個內存引用計數就有不少東西得寫,因此索性就分兩篇文章來寫,以後再寫一篇文章來介紹python的垃圾回收方式。

相關文章
相關標籤/搜索