0

0

Python源码中的内存管理机制 探索Python源码自动回收原理

看不見的法師

看不見的法師

发布时间:2025-08-04 14:33:01

|

859人浏览过

|

来源于php中文网

原创

python内存管理核心是引用计数,对象引用归零时立即释放内存,确保高效即时回收;2. 循环引用由分代垃圾回收器解决,gc通过标记-清除算法识别并清理不可达的循环引用孤岛;3. cpython对小对象使用内存池(pymalloc)策略,减少系统调用和碎片化,提升分配效率,大对象则直接由操作系统管理,整体机制保障了自动、高效、低开销的内存管理。

Python源码中的内存管理机制 探索Python源码自动回收原理

Python的内存管理机制,说白了,核心就是一套巧妙的组合拳:引用计数作为主力,配合一个专门处理循环引用的垃圾回收器。这套机制保证了我们写代码时,多数情况下不用操心内存的分配与回收,因为它在底层已经默默地把脏活累活都干了。当然,CPython作为最常用的实现,还有它自己一套针对小对象的内存池策略,这大大提升了效率。

Python源码中的内存管理机制 探索Python源码自动回收原理

解决方案

当我们谈论Python的内存管理,首先想到的就是引用计数。在CPython的源码里,每个Python对象都有一个

ob_refcnt
字段,它记录着有多少个地方“引用”着这个对象。每当一个变量引用了某个对象,或者这个对象被放进了一个容器(比如列表、字典),它的引用计数就会增加。反之,当引用被解除(比如变量超出作用域,或者从容器中移除),引用计数就会减少。一旦
ob_refcnt
归零,意味着这个对象已经没有任何地方在使用了,它的内存就会被立即释放,或者说,归还给内存分配器。这种机制的好处是即时性,内存可以很快被回收,减少了内存占用的峰值。

但引用计数有个天生的“盲点”:循环引用。试想一下,对象A引用了B,B又引用了A,即使外部已经没有对A和B的引用,它们的

ob_refcnt
永远不会归零,因为它们互相牵制着。这时候,Python的垃圾回收器(Garbage Collector, GC)就登场了。它周期性地运行,专门用来发现并清理这些无法通过引用计数释放的循环引用。CPython的GC采用的是分代回收策略,它把对象分成几代,新创建的对象属于“零代”,如果它们在几次GC扫描中都存活下来,就会被提升到“一代”,再存活下来就到“二代”。这样做的原因是,大部分对象的生命周期都很短,频繁地扫描新对象可以更快地回收内存,而对老对象的扫描频率则可以降低,以减少性能开销。GC通过一种“标记-清除”或类似算法来识别这些循环引用的孤岛:它会遍历所有对象,标记出那些可达的(即从根对象,比如全局变量、栈帧中的变量,可以访问到的)对象,然后将未被标记的对象(包括循环引用中的)清除掉。

立即学习Python免费学习笔记(深入)”;

Python源码中的内存管理机制 探索Python源码自动回收原理

除了引用计数和垃圾回收,CPython在内存分配上也有自己的优化。对于频繁创建和销毁的小对象(比如整数、浮点数、字符串等),CPython不会每次都向操作系统申请内存。它维护了一个内存池(或称竞技场、

pymalloc
。当我们需要一个小对象时,它会从自己的内存池中快速分配一块内存;当对象被回收时,这块内存也不会立即归还给操作系统,而是回到内存池中,以备下次使用。这极大地减少了系统调用的开销和内存碎片化,让小对象的创建和销毁变得非常高效。而对于大对象(比如大的列表、字典,或者超过一定阈值的字符串),CPython会直接委托给操作系统的
malloc
free
函数来处理。

Python内存管理的核心机制是什么,为什么它很重要?

Python内存管理的核心机制,毋庸置疑,就是引用计数。每个Python对象内部都有一个

ob_refcnt
字段,这个字段就是它的“生命计数器”。每当有新的引用指向这个对象,计数器就加一;每当一个引用被解除,计数器就减一。当这个计数器归零时,对象所占用的内存就会被立即回收。这种机制的重要性在于它的即时性简洁性。内存一旦不再被需要,就可以立竿见影地释放,这有助于降低程序的内存占用峰值,也能减少内存碎片。对于那些生命周期短暂、频繁创建和销毁的对象来说,引用计数效率非常高,因为它避免了复杂的全局扫描。这在很多场景下,比如处理大量临时数据时,能提供非常好的性能表现。在我看来,引用计数是Python实现“内存自动管理”最直观、最核心的基石,没有它,后续的垃圾回收机制就失去了大部分的意义,或者说,会变得异常沉重。

Python源码中的内存管理机制 探索Python源码自动回收原理

Python如何处理循环引用导致的内存泄漏问题?

引用计数虽然高效,但它有一个致命的弱点:无法处理循环引用。想象一下,两个对象A和B,A引用了B,B又引用了A,即使没有任何外部变量再引用A或B,它们的引用计数永远不会降到零,因为它们彼此“依赖”。这就导致了内存泄漏。为了解决这个问题,Python引入了垃圾回收器(Garbage Collector, GC)。这个GC不是实时运行的,它会周期性地启动,专门寻找那些引用计数不为零,但实际上已经无法从程序“根”访问到的对象集合。

VisualizeAI
VisualizeAI

用AI把你的想法变成现实

下载

CPython的GC采用的是分代回收(Generational Collection)策略。它将所有可回收的对象分为三代:0代、1代、2代。新创建的对象都在0代。每次GC运行时,会优先扫描0代对象。如果一个0代对象在一次或几次GC扫描后依然“存活”(即它的引用计数不为零,且可能参与了循环引用),它就会被提升到1代。同理,1代的对象在经历更多次GC后,如果仍存活,就会被提升到2代。这种分代的设计是基于一个经验事实:绝大多数对象的生命周期都很短。因此,频繁地扫描新对象(0代)可以高效地回收大部分垃圾,而对老对象(1代、2代)的扫描频率则可以大大降低,从而减少GC对程序性能的影响。

GC识别循环引用的过程大致是这样的:它会从所有“根”对象(比如全局变量、调用栈中的局部变量)开始,遍历所有可达的对象,并把它们标记起来。然后,GC会检查那些引用计数不为零但未被标记的对象。这些未被标记的对象就是循环引用的“孤岛”,因为它们虽然有引用,但已经无法从程序的任何活的部分访问到。一旦发现这样的循环,GC就会“断开”它们之间的引用,然后将其内存释放。你也可以通过

import gc
模块来手动控制GC,比如
gc.collect()
可以强制执行一次垃圾回收。

import gc

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

# 创建循环引用
node1 = Node("A")
node2 = Node("B")
node1.next = node2
node2.next = node1

# 此时外部对node1和node2有引用,引用计数不为0

# 解除外部引用
del node1
del node2

# 此时,node1和node2指向的对象虽然外部引用没了,但它们内部互相引用,引用计数不为0
# 它们会形成一个无法被引用计数回收的循环

# 强制执行垃圾回收
gc.collect() 
# 此时,如果这些对象是可回收的,它们就会被GC清理掉
# 实际效果可以通过gc.get_objects() 或 sys.getrefcount() 结合调试观察

CPython内部的内存分配策略是怎样的,对性能有何影响?

CPython在内存分配上,其实做了一层精巧的优化,这主要体现在对小对象的处理上。它并不是每次需要内存都直接向操作系统(OS)请求

malloc
,因为频繁的系统调用会有不小的开销,而且容易导致内存碎片。CPython为此实现了一套自己的内存管理系统,通常被称为
pymalloc
(在Python 3.x中,这套机制被进一步优化和集成)。

这套系统主要针对那些大小固定且频繁创建和销毁的小对象,比如小整数、短字符串、元组、字典等。它的核心思想是维护一个内存池(memory pool)。这个内存池由一系列arena(竞技场,通常是256KB的内存块,直接从OS申请)组成。每个arena又被细分成多个pool,每个pool专门用于存储特定大小的对象。例如,一个pool可能只存储16字节的对象,另一个可能只存储32字节的对象。

当Python需要创建一个小对象时,它会首先检查对应的pool是否有空闲的内存块。如果有,就直接从pool中快速分配。当这个小对象被回收时,它的内存块不会立即归还给操作系统,而是被标记为“空闲”,回到它所属的pool中,等待下次被复用。这种机制带来了显著的性能提升:

  1. 减少系统调用开销: 只有当arena或pool完全用尽时,CPython才需要向OS申请大块内存。小对象的分配和回收都发生在CPython内部,避免了频繁的
    malloc
    /
    free
    系统调用。
  2. 降低内存碎片化: 因为每个pool只处理特定大小的对象,内存块的分配和回收更加规整,大大减少了外部碎片(external fragmentation)的产生,提高了内存的利用率。
  3. 提高分配速度: 从预先分配好的内存池中取出一块内存,比向OS请求要快得多。

对于大对象(通常是那些大小超过512字节的对象,这个阈值可能会根据版本有所调整),CPython则直接退化到使用操作系统的

malloc
free
。这是因为大对象数量相对较少,且大小不一,为它们维护复杂的内存池意义不大,直接交给OS管理更为高效。

所以,CPython的内存分配策略是一个分层的、优化的设计:对小对象进行精细化管理以提高性能和内存利用率,对大对象则直接依赖底层操作系统。这使得Python在处理各种规模的数据和应用时,都能保持不错的内存效率。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

755

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

636

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

758

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

618

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1262

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

707

2023.08.11

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.8万人学习

Django 教程
Django 教程

共28课时 | 3.1万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号