python内存管理方式_Python内存管理(二):垃圾回收机制和内存管理机制的实现...

从上篇文章开始看,本文紧接上一篇。

五、垃圾回收机制

让我们来重温将内存比作书的比喻,假设书中的一些故事已经非常久远了。没有人正在读或者引用这些故事。如果没人在读或者引用,我们应该解决它,以便腾出空间交给新的写入操作。

哪些旧的,不被引用的数据应该和Python中的引用计数已经降为0的对象作比较。请记住,Python中的每个对象都具有引用计数和指向类型的指针。

引用计数可能会因为几种不同原因增加。比如,可能会因为你将对象赋值给别的变量而增加:

如果你将该对象作为一个参数传递,引用计数也会增加:

total = sum(numbers)

最后一个例子:如果你将对象放在一个列表中,引用计数也会增加:

matrix = [numbers, numbers, numbers]

Python允许你使用sys模块检查当前引用计数。使用sys.getrefcount(numbers),但是要注意这个检查操作把numbers作为参数传递了,所以要注意引用计数加一。

无论如何,如果在你的代码中还需要该对象,引用计数就一定比0大,一旦该对象的引用计数降为0,该对象就会被调用特定的释放函数,释放该内存,使得其他对象可以使用。

但是释放内存意味着什么?其他对象如何使用被释放的内存呢?让我们进入CPython的内存管理机制。

六、CPython的内存管理

抓紧了,我们要潜入CPython的内存结构和算法中了。

正如上面所提到的,从硬件层到CPython之间存在着很多抽象层。操作系统将物理内存抽象出来,创建了应用程序(如Python)可以访问的虚拟内存层。

特定操作系统的虚拟内存管理器为Python进程占用了大量的内存,下图的深灰色部分现在归Python进程所有。

Python将部分内存用作内部使用和非对象内存,另一部分专供对象存储(你的int,dict等)请注意,这已经被简化,想看完整图可以到如下链接:https://github.com/python/cpython/blob/7d6ddb96b34b94c1cbdf95baa94492c48426404e/Objects/obmalloc.c​github.com

CPython有一个对象分配器,响应对象内存区域中的内存分配。对象分配器就是神奇之处。每次当新对象需要分配空间或者被删除时都要叫对象分配器。通常情况下,为Python对象添加或者删除一次数据(比如int、list)并不会涉及到太多数据。所以分配器的设计已经过调整,可以一次处理少量数据。它还会倾向于在非强制要求下不分配内存。

源码中的评论将分配器形容为:“一种用于小块的快速专用内存分配器,在通用malloc的顶部使用。”现在让我们看一下CPython的内存分配策略。首先,我们讨论三个主要方面以及他们之间的关系。Arenas是最大的内存块,其在内存中的页面边界上对齐。(页面边界是操作系统使用的固定长度连续内存块的边界)Python假设系统的页面大小为256KB。

在arenas内部有池,池是一个虚拟内存页(4KB)。这些就像是我们用书比喻的时候,书页的概念。这些池被分成较小的内存块,所有的给定的池中的块都具有相同的大小等级,给定一定数量的请求数据,大小等级定义了特定的块的大小(如上所示,block并不一定一样大),大小等级到底是多少,取决于请求数据。

回顾,CPython进行内存分配时,arenas是最大的内存块,其在内存页面边界上对齐。arena内有池,池像是书本中页的概念,给定大小(4KB),pool内有不同的block,block大小由大小等级决定,大小等级由请求的数据决定。每个池中block大小一致。

比如,如果请求42bytes的内存,该数据应该被放在大小为48byte的块中。

6.1 池

池由相同大小的块组成,每个池维护一个与其块大小相同的其他池的双向链表。这样即使在不同池中也可以轻松找到指定块大小的可用空间。

usedpools列表会跟踪每种大小级别的块所在的,所有具有一定空间存储数据的池,当给定大小的块被请求时,算法就来找usedpools列表来在池列表中寻找那样大小的块。

pools自身必须有三种状态之一:used,full,empty。一个used pool有可供数据存储的块。一个full pool的内存已经被全部分配完并存储了数据。一个empty pool内没有任何数据被存储,并且可以在需要的时候为块分配任何大小级别的block。

freepools列表跟踪所有空状态下的池,但什么时候空池可以被使用?

假设你的代码需要8 bytes 的数据块,如果在usedpools中没有8 bytes这样的大小级别(block)的池,一个新的空池就会被初始化来存储8 bytes大小级别的数据。然后这个新池就会被加入usedpools列表,以便满足未来的请求。

假设一个full pool因为不再需要某些内存而释放了它的一些块,该池将会被添加回其大小级别的usedpools列表中。你现在可以看到池之间是如何通过他们的算法,在不同状态之间自由切换的了。

6.2 块

正如上图所示,池包含一个指向其空内存块的指针。这和上面有一些小差别。根据源码中的评论,该分配器一直在所有的级别(arena,pool,block)中努力,在真的需要内存之前不会去碰内存的任何部分。这意味着一个池中的块也有三种状态:untouched: 未被分配的一部分内存

free: 被分配但是后来被释放的一部分内存,不再包含相关数据

allocated: 包含着相关数据的一部分内存

freeblock指针指向内存的空闲块的单链表。换句话说,是存储了数据可用位置的列表。如果需要可用空闲块更多,分配器将在池中获取一些untouched块。

当内存管理器让块空闲时,现在将这些空闲块加入freeblock 单链表的最前面。实际的列表可能不是连续的内存块(像第一张图那样),而是像下图一样:

6.3 Arenas

Arenas存储着pools,这些pools可能是used、full或者empty的,arenas本身并没有像pools那样的清晰状态(三种状态)

arenas 是被组织到一个usable_arenas的双向链表里,这个列表通过可用空闲池的数量排序,空闲池越少,这样的arenas就越在list前面(优先填满一个)

为什么不把数据优先放在空闲池更多,有更多空间的地方?

这让我们想到真正释放内存的思想。你会注意到,我在句子中一直使用释放(free)这个词,原因是当一个block被认为是空闲的时候,该部分内存实际上并没有释放回系统内存,Python进程会对其进行分配,并在以后将其用于新数据。真正释放内存是将内存还给操作系统。

arenas是唯一可以被真正释放的。所以,有理由认为应该让那些更接近空的arenas变空并且将其真正释放,以此来减少Python整体内存占用。

七、结论

内存管理是使用计算机必不可少的一部分,无论好坏,Python几乎会在后台处理一切内存管理的问题。

本文我们学到了:内存管理是什么,为什么重要;

默认的Python实现CPython是用C写的;

CPython的内存管理中,数据结构和算法是如何处理你的数据的

Python抽象出许多使用计算机的严格细节,这让我们在更高层次进行开发,而不用担心所有字节的存储方式和位置。

到此,Python内存管理的内容就介绍结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值