编程语言
首页 > 编程语言> > Python 中的内存管理

Python 中的内存管理

作者:互联网

当涉及到用于管理内存的主要方法时,有两种主要方法可以处理两种不同的用例:

  1. 引用计数
  2. 使用标记和扫描进行循环检测

在进一步了解这些方法之前,了解有两种类型的内存位置 — 堆栈是很有用的。

这里的堆不是我们通常知道的堆数据结构,而是一种执行内存管理操作的内存管理结构,例如内存的分配和释放,主要由 Python 运行时管理。

至于存储在哪里,重要的是要注意,本地范围内的所有函数调用和变量都存储在堆栈中,而包括类和数据结构在内的任何对象都存储在堆中。作为一般的经验法则,如果可以事先预测数据的确切大小 - 在大多数情况下,它将存储在堆栈中。但是,它可能因具体情况而异。

引用计数

创建变量时,该变量的引用存储在内存中,就像在其他语言中一样。您可以使用该函数检查对象的内存位置。这些引用通过维护这些引用的计数来维护在内存中。id

>>> a = 'crunchcode'>>>  id(a)140654166163184>>>
 hex(id(a)
)
'0x7fec999c3ef0'

有很多方法可以获取引用计数,但是,我喜欢使用该模块,因为它的简洁性。ctypes

>>> import ctypes
>>> ctypes.c_long.from_address(id(a)).value
1

这显示了我们存储在内存中的指向对象的引用数量。crunchcode

让我们看看当我将新变量分配给(现有变量)时会发生什么。ba

>>> import ctypes
>>> a = [79]
>>> ctypes.c_long.from_address(id(a)).value 1
>>> b = a>>> ctypes.c_long.from_address  (id(a
)).value 2

正如您现在可能注意到的那样,我们的参考计数增加了 1。这是因为两个变量都引用相同的内存地址。ab

为了更好地理解这一点,我们需要了解副本和副本之间的另一个概念差异。shallowdeep

通常,当我们在 Python 中将一个变量分配给另一个变量时,如上例所示,python 将其引用存储到相同的内存位置,并将引用计数增加 1。这称为副本。shallow

这也可能会在代码中产生意想不到的效果:

>>> a = [1, 2]>>> b = a>>> a[1, 2]>>> b[1, 2]>>> a

[1]


 = 100    >>> b

[1, 100
]

但是,对于字符串,不会观察到相同的行为:

>>> a = 'crunchcode'>>> b = a>>> a
 = 'aa'>>> b'crunchcode' 


刚刚到底发生了什么?字符串是不可变的。每次更改值时都会创建一个新字符串,即使内容相同也是如此。这是一个完全不同的主题,超出了本文的范围,我打算在未来介绍。

现在让我们谈谈deepcopy

>>>  import copy>>> import ctypes
>>> a = [1, 2]>>> b = copy.deepcopy
(a)>>> id(a)140478929984320
>>> id(b)


140478929986816
     >>>  a[1] =
100
>>> b
[1, 2]>>> a[1, 100]
py
>>> ctypes.c_long.from_address(id(a
)).value 2
>>> ctypes.c_long.from_address(id(b)).value
1 

当我们使用 时,Python 运行时不会将变量指向相同的内存地址,相反,它实际上复制内存地址中存在的值并分配一个新的内存位置,如上面的不同结果所证明的那样。因此,每个变量的引用计数也会单独存储。deepcopyid

一旦运行时检测到特定对象的引用计数为 0,就会释放内存空间。

周期检测

Python 还在其垃圾回收器中实现了一种名为 Mark and Sweep 的算法,以释放具有循环引用的内存。

该算法的解释超出了本文的范围,但是,如果您愿意,可以在Mark-and-Sweep:垃圾收集算法 - GeeksforGeeks上了解更多信息。

但是,当执行标记和扫描算法时,现在释放了被占用的内存的微小片段。如果多次执行此操作,则会导致称为碎片的问题,其中连续的内存块不再是真正连续的,从而导致空间量越来越少,最终导致内存异常。可以运行碎片整理算法来解决此问题,但是,这会导致额外的开销。

标签:Python,内存,堆栈
来源: