往期文章

跟着我学Python基础篇:01.初露端倪
跟着我学Python基础篇:02.数字与字符串编程
跟着我学Python基础篇:03.选择结构
跟着我学Python基础篇:04.循环
跟着我学Python基础篇:05.函数
跟着我学Python基础篇:06.列表
跟着我学Python基础篇:07.文本
跟着我学Python基础篇:08.集合和字典


跟着我学Python进阶篇:01.试用Python完成一些简单问题
跟着我学Python进阶篇:02.面向对象(上)
跟着我学Python进阶篇:03.面向对象(下)
跟着我学Python进阶篇:04. 错误和异常
跟着我学Python进阶篇:05. 错误


1. 内存管理概述

1.1 为什么要进行内存管理

内存管理是指软件运行时,对计算机内部资源的分配和使用技术。其最主要目的是高效、快速地分配,并且在适当的时候释放和回收资源。

在Python中,一切类型的实例均为对象,内存管理的目标就是这些对象。在Python这门脚本语言中,程序员已经不关心如何管理内存了,这些工作都会交给解释器来做,但是,对于实际编程来说,理解内存管理的能力与局限性将有助于程序员更好地开发程序。

1.2 内存管理的常用机制

(1)引用计数机制
Python内部记录着所有对象的引用数量,该数量使用一个内部跟踪变量来存储,这个跟踪变量就是引用计数器。一旦对象被创建,它的引用计数器数值从0变成1,对象的引用计数,随着对象引用数量的增多而增多,对象的引用数量每增加一个,计数器的值就增加1,反之则减1,直到值为0,就会被当作垃圾进行回收。

(2)垃圾回收机制
当内存不再使用某个对象时,垃圾收集器就会把他们处理掉,它会去检查那些引用计数为0的对象,然后清理掉所在的内存空间。

(3)内存池化机制
Python的垃圾回收机制会清理不再使用的内存,但是它并不是将不用的内存返回给操作系统,而是放到内存池中,内存池机制用于管理小块内存的申请和释放。

上面所讲的这些机制,引用计数机制其实也是一种垃圾回收机制。但是引用计数机制潜伏着两个问题,一是降低垃圾回收效率,二是解决不了循环引用问题。为了弥补这些不足,垃圾回收机制采用以引用计数技术为主,标记清除和分代收集技术为辅的战略,对垃圾收集的功能进行完善。

2. 引用计数机制

在Python中,大多数对象的生命周期都是通过对象的引用计数来管理的,从广义上来讲,引用计数机制也是一种垃圾回收机制,而是一种最直观、最简单的垃圾收集技术。

2.1 引用计数机制概述

引用的概念我们之前已经介绍过。
为了让每个对象知道指向它的引用数量,Python引入了引用计数器的概念。Python中每个对象都拥有一个引用计数器,表示有多少个引用指向该对象。引用计数器使用整数表示,可以通过sys包的getrefcount()函数查看该计数器的数值。需要注意的是,当使用某个引用作为参数传递给getrefcount()函数时,该参数会创建一个临时的引用,所以调用函数得到的结果比期望值多1。

from sys import getrefcount

list_one=[1,2,3,4,5]
print(getrefcount(list_one))
list_two=list_one
print(getrefcount(list_two))

跟着我学Python进阶篇:06. 内存管理-LMLPHP

注意:由于Python会缓存一些数字和字符串,所以使用getrefcount函数获取到这些对象引用计数器数值会很大,是无法准确地获取引用计数器的数值的。

2.2 增加对象的引用

每次创建或者复制一个指向对象的引用时,会让该对象引用计数设置为1,如果其他对象要使用该对象,就会增加该对象的引用数量,从而让该对象的引用计数器数值增加,具体增加该对象的引用数量的操作如下场景:
(1)当同一个对象的引用或者对象又被赋值给其他变量时,会让该对象的引用计数器数量值增加1.
(2)当对象作为参数传递给函数时,会让该对象的引用计数器数值增加1.
(3)当某个对象加入容器中成为其内部元素时,会让该对象的引用计数器数值增加。

2.3 减少对象的引用

与增加引用的操作相反,只要某个对象的引用被销毁时,就让该对象的引用计数器数值减少。
(1)当对象引用离开其作用范围时,会让该对象的引用计数器数值减少1,这种情况经常出现在函数运行结束时,其内部的所有局部变量都被自动销毁。
(2)使用def语句显式地销毁对象的引用。
(3)当把另外一个对象赋值给变量后,原对象的引用计数器数值自动减少1.
(4)当从容器对象中删除某个对象时,会让该对象的引用计数器数值减少1.
(5)当容器对象本身被销毁,或者容器对象离开其作用范围时,会让其内部的所有对象的引用计数器数值减少1.

2.4 释放对象占用的内存

只要某个对象的引用计数器数值为0,就意味着已经没有任何其他对象使用这个对象了,此时可以将其占用的内存空间进行回收。

class Test:
    def __del__(self):
        print('---end---')

from sys import getrefcount
test=Test()
getrefcount(test)
del test

跟着我学Python进阶篇:06. 内存管理-LMLPHP
与其他主流垃圾收集技术相比,引用计数最大的优点就是实时性,一旦没有任何引用指向一块内存,该内存就会被当作垃圾进行回收,而其他垃圾收集技术只能在特殊条件下,比如内存分配失败,才能进行无效内存回收。

3. 容器对象引起的循环引用

3.1 容器对象引用对象

当某个对象或者引用加入容器中,该对象或者引用所指向的对象引用计数器的值会增加。

3.2 对象之间循环引用

循环引用是指多个对象之间相互引用。循环引用的一组对象引用计数都不为0,然而并没有任何外部对象引用他们。这就意味着外部对象不会使用这组对象,应该及时回收这组对象所占用的内存空间。不过由于循环引用的存在,这组对象的任意一个的引用计数都不为0,所以这组对象占用的内存空间永远得不到释放。
对于垃圾回收机制来说,循环引用的问题是致命的,它与手动内存管理产生的内存泄露没有任何区别。为了解决循环引用,Python提供了弱引用技术和标记清除技术。

4. 弱引用解决自定义对象的循环引用

循环引用的一组对象,它们既不能被其他对象使用,又不能被当作垃圾回收,它们会一直驻留在内存中,直至程序结束。程序如果持续产生这种不可使用的对象,就会内存泄露。因此,Python引用弱引用技术,解决对象之间产生循环引用。

4.1 弱引用概述

在计算机程序设计中,弱引用是指不能确保其指向对象不会被垃圾收集器回收的引用,也就是说,如果一个对象只被弱引用所使用,则该对象可能在任何时刻被回收。强引用与弱引用正好相反,一个对象只要被强引用所引用,则对象肯定不会被回收。
Python中使用weakref模块创建对象弱引用,一旦某个对象的引用计数器值为0,或者只存在对象的弱引用时,就会回收该对象占用的内存空间。

创建对象弱引用可以使用如下两种方式:

  1. weakref.ref函数创建弱引用
weakref.ref(object[,callback])
  1. weakref.proxy函数创建弱引用
weakref.proxy(object[,callback])

4.2 弱引用处理循环引用

当两个对象之间出现循环引用时,可以进一步使用强引用指向,另外一方使用弱引用指向。这样,当没有任何其他引用指向这两个对象时,由于弱引用没有特有对象的能力,所以它指向的对象的内存会优先得到释放。

5. 垃圾回收机制

5.1 垃圾回收机制概述

前面所介绍的引用计数是最为直观、简单的垃圾回收技术,其基本原理就是,当某个对象的引用计数器数值为0,说明没有任何引用指向该对象,此时,这个对象就会被当作垃圾进行回收。
不过,当容器对象产生循环引用时,引用计数技术就无能为力了,为了解决上述问题,Python又在gc模块中补充了标记清楚技术和分代垃圾回收技术。其中标记清除技术负责解决容器对象产生的循环引用问题,分代回收技术负责提高垃圾回收的效率。

5.2 标记清除技术

标记清除技术是一种基于追踪回收技术实现的垃圾回收算法,它依赖于对所有存活的对象进行一次全局遍历,确定哪些对象可以回收。遍历过程如下:

  1. 标记阶段,gc模块会把所有存活对象打上标记。
  2. 清除阶段,把那些没有标记的垃圾对象进行回收。

对象间通过引用连在一起,构成有向图,对象则构成这个有向图的结点,引用关系构成有向图的边。首先要寻找root object(全局变量,调用栈,寄存器,不能被回收)从这个根对象出发,沿着有向边遍历对象,可达的对象标记为活动对象,不可达的对象就是要被清除的非活动对象。
实际上,标记清除不会改动真实的引用计数,而是将集合的引用计数复制一份副本,改动该对象的引用副本,这样,对副本做任何改动,对不会影响到对象的生命周期维护。
标记清除技术可以自然地处理引用循环问题,在创建和销毁对象时少了操作引用计数值的开销,但是在垃圾回收器运行过程中,应用程序必须暂时停止,而分代垃圾回收正好减少了它的停顿时间。

5.3 分代回收技术

gc模块利用“空间换时间”的策略(分代回收)减少垃圾回收中扫描的频率。
Python将系统中所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个代,内存分为三个代,分别为第0代(年轻代),第一代(中年代),第二代(老年代),他们对应着三个链表。

垃圾收集的频率随着代的存活时间增大而减小,新创建的对象都会分配在年轻代,年轻代链表总数达到上限时,垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就转移到中年代。
以此类推,老年代中的对象是存活时间最久的对象。

5.4 gc模块概述

在Python中,垃圾回收(Garbage Collection)是自动管理内存的重要机制之一。Python的垃圾回收机制通过跟踪对象的引用计数和使用分代回收算法来实现对内存的管理和释放。

Python提供了一个名为gc的标准模块,用于控制和调整垃圾回收机制的行为。可以使用gc模块中的函数来手动触发垃圾回收、查看内存使用情况、设置垃圾回收的选项等。

  1. gc.collect(): 手动触发垃圾回收。
  2. gc.get_count(): 返回当前自动垃圾回收的计数器。
  3. gc.get_stats(): 返回垃圾回收的统计信息。
  4. gc.set_threshold(): 设置垃圾回收的阈值。

6. 内存池机制

6.1 小整数对象池

整数在程序中使用非常广泛,为了避免整数频繁地申请和销毁空间内存,Python对小整数使用了对象池技术,所谓小整数是指位于【-5,,256】范围内的整数,这些整数是提前创建好的,不会被当成垃圾回收。
需要注意的是,只包含单个字符的字符串是共用的,会一直驻留在内存中。

6.2 字符串的intern机制

Python解释器提供了intern技术提高字符串的使用效率。
intern机制是指同样的字符串对象只保存一份,该字符串会放到缓存池中,是共用的。字符串对象不会一直留在缓存池中,它使用引用计数机制维护,只要这个字符串对象的引用计数器为0,就会从缓存池中销毁。
不过,如果字符串中包含有空格、#、!等特殊符号,则Python解释器不会开启intern机制,多个引用不会共用该字符串对象。

03-01 22:33