zoukankan      html  css  js  c++  java
  • 【python测试开发栈】python内存管理机制(一)—引用计数

    什么是内存

    在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存。我们都知道计算机的CPU相当于人类的大脑,其运算速度非常的快,而我们平时写的数据,比如:文档、代码等都是存储在磁盘上的。磁盘的存取速度完全不能匹配cpu的运算速度,因此就需要一个中间层来适配两者的不对等,内存由此而来,内存的存取速率很快,但是存储空间不大。

    举一个图书馆的例子,便于大家理解,我们图书馆的书架就相当于磁盘,存放了大量的图书可以供我们阅读,但是如果书放在书架上,我们没办法直接阅读(效率低),只能将书取出来,放在书桌上看,那书桌就相当于内存。

    内存回收

    内存资源毕竟是有限的,所以在使用之后,必须被回收掉,否则系统运行一段时间后就会因无内存可用而瘫痪。我们软件测试领域常用的两种语言:java和python,全部都采用内存自动回收的方法,也就是我们只管申请内存,但是不管释放内存,由jvm和python解释器来定期触发内存回收。作为对比,C语言和C++中,程序员需要使用malloc申请内存,使用free去释放内存,malloc和free必须成对的出现,否则非常容易出现内存问题。

    还拿上面图书馆的例子,假如图书馆的书看完之后放在书桌上就可以(因为图书可自动回收),那么很快的,就没有位置给新进来的同学看书了。这时候就需要图书馆管理员(jvm或python解释器)定期的回收图书,清空书桌。不过正常情况下,我们离开图书馆时,要自己清空书桌,将书放回书架(类似C语言和C++的内存回收方式)。

    python内存管理

    引用计数

    python通过引用计数来进行内存管理,每一个python对象,都维护了一个指向该对象的引用计数。python的sys库提供了getrefcount()函数来获取对象的引用计数。下面我们看个例子(注意:不同版本的python,运行结果不同,我这里采用的是python3.7.4):

    """
        @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,对引用变量个数有影响(主要是可复用的对象)。我们先贴出来运行结果,再来分析产生结果的原因:

    27
    4
    9
    3
    this is init
    2
    3
    2
    

    不过提前声明一点:sys.getrefcount函数在使用时,因为将对象(比如上例中的str1)作为参数传入,所以会额外增加一个变量(相当于getrefcount持有了str1的引用),因此实际每个对象的实际引用计数都得减1。下面分别介绍下上面的几种情况:

    • 字符串: 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和str2、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的垃圾回收方式。

  • 相关阅读:
    【转】VS2010中 C++创建DLL图解
    [转]error: 'retainCount' is unavailable: not available in automatic reference counting mode
    [转]关于NSAutoreleasePool' is unavailable: not available in automatic reference counting mode的解决方法
    【转】 Tomcat v7.0 Server at localhost was unable to start within 45
    【转】Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If
    【转】SVN管理多个项目版本库
    【转】eclipse安装SVN插件的两种方法
    【转】MYSQL启用日志,和查看日志
    【转】Repository has not been enabled to accept revision propchanges
    【转】SVN库的迁移
  • 原文地址:https://www.cnblogs.com/zhouliweiblog/p/11946819.html
Copyright © 2011-2022 走看看