python 内存管理机制

a =20

python中创建的对象的时候,首先会去申请内存地址,然后对对象进行初始化,所有对象都会维护在一个叫做refchain的双向循环链表中,每个数据都保存如下信息

  • 链表中数据前后数据的指针

  • 数据的类型

  • 数据值

  • 数据的引用计数

  • 数据的长度(list,dict..)

一、引用计数机制

引用计数增加:

  • 对象被创建

  • 对象被别的变量引用(另外起了个名字)

  • 对象被作为元素,放在容器中(比如被当作元素放在列表中)

  • 对象被当成参数传递到函数中

引用计数减少:

  • 对象的别名被显式的销毁

  • 对象的一个别名被赋值给其他对象 (例:比如原来的a=10,被改成a=100,那么此时10的引用计数就减少了)

  • 对象从容器中被移除,或者容器被销毁(例:对象从列表中被移除,或者列表被销毁)

  • 一个引用离开了它的作用域(调用函数的时候传进去的参数,在函数运行结束后,该参数的引用即被销毁)

查看引用计数

在 Python 中,可以使用 sys 模块中的 getrefcount() 函数来查看对象的引用计数。

具体操作如下:

import sys

# 创建一个列表对象
my_list = [1, 2, 3]

# 查看 my_list 对象的引用计数
print(sys.getrefcount(my_list))

输出结果为:

2

这里输出的结果是 2,而不是 1。这是因为 getrefcount() 函数会在内部临时增加对象的引用计数,以保证它在函数调用期间不会被回收。因此,实际上有两个引用指向 my_list 对象:一个是函数本身的参数,另一个是 my_list 变量本身。如果要准确地查看对象的引用计数,应该排除掉这个临时引用计数,例如:

import sys

# 创建一个列表对象
my_list = [1, 2, 3]

# 查看 my_list 对象的引用计数(减去 getrefcount() 函数本身的引用)
print(sys.getrefcount(my_list) - 1)

输出结果为:

1

这里输出的结果就是 my_list 对象的真实引用计数了。需要注意的是,虽然可以使用 getrefcount() 函数来查看对象的引用计数,但不建议在实际代码中使用它来判断对象是否可回收或者是否存在循环引用等问题,这些情况应该使用垃圾回收机制来处理。

二、数据池和缓存

1、小整数池

对于整数对象,Python 会自动缓存所有数值范围在 [-5, 256] 内的整数对象,这些对象在程序运行期间始终存在于内存中,不会被回收。如果多个变量引用同一个数值在这个范围内的整数对象,实际上它们都是指向同一个内存地址的同一个对象,这样可以避免频繁创建和销毁对象。

2.intern机制

intern 机制是一种优化技术,用于将一些字符串对象缓存起来并重复使用,从而减少内存开销和提高程序性能。

具体来说,当 Python 解释器遇到一个字符串字面量(例如:"hello")时,它会先检查该字符串是否已经存在于 intern 字符串池中。如果存在,那么 Python 会直接返回 intern 字符串池中的对应字符串对象的引用;否则,Python 会创建一个新的字符串对象,并添加到 intern 字符串池中,然后返回新创建的字符串对象的引用,并标记为 intern 对象。

intern机制的优点是,在创建新的字符串对象时,会先在缓存池里面找是否有已经存在的值相同的对象(标识符,即只包含数字、字母、下划线的字符串),如果有,则直接拿过来用(引用),避免频繁的创建和销毁内存,提升效率。

3.缓存机制

  • float缓存100个对象

  • dict、list等一些内置的数据类型,会缓存80个对象

  • 元组 会根据元组数据的长度,分别缓存元组长度为0-20的对象

三:标记清除

引用计数存在一个缺点:那就是当两个对象出现循环引用的时候,那么这个两个变量始终不会被销毁,这样就会导致内存泄漏。

标记清除就是为了解决上述循环引用的情况

标记清除(mark and sweep)是一种垃圾回收算法,用于自动管理不再使用的内存空间,避免内存泄漏和资源浪费。该算法基于可达性分析的基础上,通过分两个阶段实现垃圾回收。

第一阶段是标记阶段(mark),系统从根节点开始遍历对象图,标记所有可以访问到的对象,以及它们的引用关系。这样就能确定哪些对象是“活”的,哪些是“死”的。

第二阶段是清除阶段(sweep),系统遍历整个堆内存,将未被标记的对象删除,并将它们所占用的内存空间释放出来,以便其他对象使用。清除的对象包括被标记为“死”的对象和没有被标记的“垃圾”对象。

标记清除算法的优点在于简单、可靠、高效。但是,它也存在一些缺点:

  1. 清理内存的过程可能会导致程序暂停,影响用户体验。

  2. 在清理内存时,如果剩余内存碎片较多,将会浪费一部分内存空间,导致内存利用率下降。

  3. 随着堆内存的增大,垃圾回收的时间也会增加,对程序的性能会造成一定影响。

总之,标记清除算法是一种广泛应用于各种编程语言中的垃圾回收算法,具有简单、可靠、高效等优点。但是,在实际应用中,应该根据程序的特点选择合适的算法,或者将多种垃圾回收算法进行组合使用,以达到更好的效果。

四:分代回收

Python 分代回收是一种垃圾回收算法,与标记清除算法相比,分代回收算法更加高效,可以有效地避免全面扫描整个堆内存的性能问题。

Python 的分代回收算法基于对象的生命周期来进行划分。首先将所有对象划分为三代,即一代、二代和三代,分别对应着对象创建后不同的生命周期。一代对象最年轻,从创建时开始算起,经过一次垃圾回收后仍然存活下来的对象被移到二代中,同样,经过二次垃圾回收后仍然存活下来的对象被移到三代中。

Python 中采用了两个策略来实现分代回收算法:

  1. 增量式垃圾回收:Python 采用了增量式垃圾回收算法,大大减少了程序暂停的时间,并提高了垃圾回收的效率和时间特性。

  2. 分代式垃圾回收:Python 将内存分为三代,针对每一代对象的特点采用不同的垃圾回收策略,从而提高垃圾回收的效率。

在 Python 中,垃圾回收器会根据每代对象的特点采用不同的策略,如在第一代中,采用扫描算法进行垃圾回收,可以快速识别活动对象;在第二代中,采用标记清除算法进行垃圾回收,即标记那些不再存活的对象进行回收,从而提高垃圾回收的效率。对于第三代中的对象,Python 回收器会将其放入“集合”中,并对其进行定期的回收。

总之,Python 分代回收算法是一种高效的垃圾回收算法,通过将内存空间分成不同的代,可以更好地处理内存分配和垃圾回收问题,提高程序的性能和效率。