• Python的内存管理机制简述


    ——本文是一篇某社团的招新笔试报告,博主始于兴趣,参考了大量网络资料而成。(参考资料见文末)

    0.引言

      本文主要介绍Python的内存管理与垃圾回收机制。了解Python的内存管理机制能够使我们加深对Python面向对象编程特性的理解、提高编程熟练程度,避免因不清楚Python的变量命名、数据存储等底层原理而陷入对Python语言不严谨的批评中。

      首先说明一下Python的变量存储方式。有句话说“Python中万物皆对象”,原因是Python中对“对象”的概念很宽泛,任何类型的变量,无论是整数、浮点数、字符还是指针、列表、函数都能作为参数(对象)传入函数中。这正是Python具有的面向对象编程的特性。因此,Python的存储问题是对象的存储问题,对于每个对象,Python会分配一块内存空间去存储。一方面,对于整数和短小的字符等,Python会执行缓存机制,即将这些对象进行缓存,不会为相同的对象分配多个内存空间。另一方面,对于容器对象,如列表、元组、字典等,通过复制拷贝或引用这些容器对象而建立的其他对象,仅仅是对象本身的引用,存储原始对象的地址,并不重新存储这些对象本身。针对这点,一个常见的例子应用是浅拷贝与深拷贝的差异,举例来讲,如果要真正拷贝出一个“独立”的列表,应该使用copy库中的copy.deepcopy()函数,而不是copy.copy()函数。

      其次,简要介绍一下Python的内存机制。Python的底层第0层是由C语言编写的Python解释器,因此Python程序是用Python封装好、由底层的C实现运行的。另外,由于Python的运行需要挂钩解释器,因此多个线程不能同时运行。即使使用GIL库,高频切换占用解释器的线程,实际上也没有使用CPU的多核实现真正的并行,使用GIL的Python多线程操作是伪装的多线程并行。Python的内存机制呈现金字塔状:

    •  -1,-2层主要由操作系统进行操作。
    •  第0层是C中的malloc,free等内存分配和释放函数进行操作;
    •  第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;
    •  第3层是最上层,也就是我们对Python对象的直接操作;底层代码是由C语言完成的。

      Python的内存管理机制主要由三部分组成,即引用计数机制、垃圾回收机制与内存池机制,以下将分别说明。

    1.引用计数机制

      如引言中所说,在Python中,整数和短小的字符,都会以缓存的方式储存这些对象,以便重复使用。赋值语句只是创造了新的引用,而不是对象本身。长的字符串和其它对象可以有多个相同的对象,可以使用赋值语句创建出新的对象。每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。当引用计数减少为0时,Python就会自动回收相关的内存,这一点类似于C++11中的智能指针shared_ptr的托管功能。

      当我们创建多个等于1的引用时,实际上是让所有这些引用指向同一个对象。为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指向的对象是否相同。

    a = 1
    b = 1

      这里,1是内存中的对象,a和b都是对象1的引用。可以通过内置函数id()返回对象的地址。

    print id(a)  #43220320(内存中某个固定的地址,这个地址存放常数1)
    print id(b)  #43220320
    print (a is b)  #True

      为了更好地理解,可以使用sys库中的sys.getrefcount()函数获得引用计数。需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。例如下面这段代码:

    a = 1
    print (getrefcount(a))
    #2

    总的来说,引用计数增加有以下几种情况:

    •  对象被创建:x=4
    •  通过拷贝创建另一个对象:y=x
    •  被作为参数传递给函数:f(x)
    •  作为容器对象的一个元素:如,Lst=[1, x,‘abc’]

    引用计数减少的情况为:

    •  一个本地引用离开了它的作用域。比如上面的f(x)函数结束时,x指向的对象引用减1
    •   对象的别名被显式销毁:del x ;或者del y
    •   对象的一个别名被赋值给其他对象:x=100
    •  对象从一个窗口对象中移除:myList.remove(x)
    •  窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。

    2.垃圾回收机制

      实际上,引用计数就是一种垃圾回收机制,也是最直观,最简单的垃圾回收技术。当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就要被回收。但若出现循环引用,引用计数机制就不再有效。这时,我们引入其他垃圾回收机制,如标记清除机制和分代回收机制。

    2.1标记清除机制

      如果两个对象的引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非0,但实际上有效的引用计数为0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。循环引用的基本形式如下:

    a = []
    b = a
    a.append(b)
    b.append(a)
    … …

      在实际操作中,并不改动真实的引用计数,而是将集合中对象的引用计数复制一份副本,改动该对象引用的副本。对于副本做任何的改动,都不会影响到对象生命周期的维护。

      这个计数副本的唯一作用是寻找root object集合(该集合中的对象是不能被回收的)。当成功寻找到root object集合之后,首先将现在的内存链表一分为二,一条链表中维护root object集合,成为root链表,而另外一条链表中维护剩下的对象,成为unreachable链表。之所以要剖成两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象,直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

    2.2分代回收机制

      从“标记-清除”这样的垃圾回收机制来看,这种垃圾回收机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少。反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。

      举例说明:

      当某些内存块M经过了3次垃圾回收的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾回收开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾回收机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

    3.内存池机制

      为了避免频繁的申请和删除内存所造成系统切换于用户态和核心态的开销,Python引入了内存池机制,专门用来管理小内存的申请和释放。

      整个小块内存的内存池可以视为一个层次结构,其一共分为4层,从下之上分别是block、pool、arena和内存池[1]。需要说明的是:block、pool和area都是代码中可以找到的实体,而最顶层的内存池只是一个概念的抽象,表示Python对于整个小块内存分配和释放行为的内存管理机制。注意,内存大小以256字节为界限,大于则通过malloc进行分配,小于则通过内存池分配。

      Python的金字塔多层内存机制在引言中已有简要介绍。底层第0层是由C实现的,Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。第1层是在第0层的基础之上对其提供的接口进行了统一的封装,这是因为虽然不同的操作系统都提供标准定义的内存管理接口,但是对于某些特殊的情况不同的操作系统都不同的行为,比如说调用malloc(0),有的操作系统会返回NULL,表示内存申请失败;然而有的操作系统会返回一个貌似正常的指针,但是这个指针所指的内存并不是有效的。为了广泛的移植性,Python必须保证相同的语义一定代表相同的运行行为。在第2层内存管理机制上,Python构建了更高抽象的内存管理策略,比如说一些常用对象,包括整数对象、字符串对象等等。第3层主要是对象缓冲池机制,它基于在第二层的内存池。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。

      Python内部默认的小块内存与大块内存的分界点定在256个字节,当申请的内存小于256字节时,PyObject_Malloc会在内存池中申请内存;当申请的内存大于256字节时,PyObject_Malloc的行为将蜕化为malloc的行为。当然,通过修改Python源代码可以改变这个默认值,从而改变Python的默认内存管理行为。

      更具体的,对于不同的对象类型,如int、string等等,Python有不同的“内存池”进行缓存。另外,Python2和Python3相对于其他编程语言,对于不同类型的变量的大小定义也不同。举例来说,Python3中int的长度实际上是其他语言中long long int的长度。

    4.总结

      本文对Python的内存管理机制进行了初步概括与说明,旨在探究Python语言创建变量、存储数据与管理内存背后的原理,以便加深对Python编程实现过程的理解。本文参考的相关文献见附录,限于时间与篇幅,本文有很多细节没有覆盖,可以参考列出的其他文献进行下一步研究。

    参考文献:

    1. Python内存管理机制
    https://blog.csdn.net/AlertBear/article/details/50808178?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

    2. python深入之python内存管理机制(重点)
    https://blog.csdn.net/zx870121209/article/details/81363311?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

    3. Python 内存管理机制和垃圾回收机制
    https://blog.csdn.net/fu6543210/article/details/82078466

    4. Python内存池管理与缓冲池设计
    https://blog.csdn.net/zhzhl202/article/details/7547445

    其他资料:

    5. 《Python源码剖析》 非常好的一本介绍Python源码的书籍,里面介绍了Python的对象机制、虚拟机、类、内存管理、垃圾回收等各个方面

    6. 《Python源码剖析》作者的博客,专门介绍CPython源码的专栏
    http://blog.donews.com/lemur/?s=Python%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90Python

    7. Python官方文档http://docs.python.org/library/index.html

    8. 缓存池在数据库中的应用:Oracle中的Buffer cache的分析
    http://space.itpub.net/9842/viewspace-399665



    [1] 注:1.block:最小的内存单元,大小为8的整数倍。有很多种类的block,不同种类的block都有不同的内存大小,申请内存的时候只需要找到适合自身大小的block即可,当然申请的内存也是存在一个上限,如果超过这个上限,则退化到使用最底层的malloc进行申请。

    2.pool:一个pool管理着一堆有固定大小的内存块,其大小通常为一个系统内存页的大小。

    3.arena:多个pool组合成一个arena。

    4.内存池:整体的抽象概念。

    用代码改变世界!就是这样,喵!
  • 相关阅读:
    UIActivityIndicatorView的详细使用
    iOS开发多线程篇—GCD的常见用法
    UIScrollView的属性总结
    关于UIView的autoresizingMask属性的研究
    Robot FrameWork 教程链接
    数据恢复基础知识
    数据恢复基础知识
    selenium webdriver 学习笔记(三)
    selenium webdriver 学习笔记(二)
    selenium webdriver 学习笔记(一)
  • 原文地址:https://www.cnblogs.com/Song-Meow/p/13490882.html
Copyright © 2020-2023  润新知